I have a mask here (the Sun Goku hair) , and I want to put a face to this mask.
Setup the base view
ViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #import "ViewController.h" @interface ViewController () <UIActionSheetDelegate >@property (nonatomic ) UIImageView *maskView;@property (nonatomic ) UIImageView *cropImageView;@property (nonatomic ) UIButton *photoButton;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; _maskView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"sungoku" ]]; _maskView.center = CGPointMake (CGRectGetWidth (self .view.frame) / 2.0 , (CGRectGetHeight (self .view.frame) / 2.0 ) - 30 ); [self .view addSubview:_maskView]; _cropImageView = [[UIImageView alloc] initWithFrame:CGRectMake (CGRectGetMinX (kCropFrame) + CGRectGetMinX (_maskView.frame), CGRectGetMinY (kCropFrame) + CGRectGetMinY (_maskView.frame), CGRectGetWidth (kCropFrame), CGRectGetHeight (kCropFrame))]; [self .view insertSubview:_cropImageView belowSubview:_maskView]; _photoButton = [UIButton buttonWithType:UIButtonTypeCustom ]; _photoButton.frame = CGRectMake (20 , CGRectGetMaxY (_maskView.frame) + 40 , CGRectGetWidth (self .view.frame) - 40 , 40 ); _photoButton.layer.cornerRadius = 5 ; _photoButton.backgroundColor = [UIColor colorWithRed:0.2 green:0.6 blue:0.8 alpha:1 ]; _photoButton.titleLabel.textColor = [UIColor whiteColor]; [_photoButton setTitle:@"Photo" forState:UIControlStateNormal ]; [_photoButton addTarget:self action:@selector (photoTapped:) forControlEvents:UIControlEventTouchUpInside ]; [self .view addSubview:_photoButton]; } - (void )didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - event - (void )photoTapped:(UIButton *)sender { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Take Photo" , @"Choose Existing Photo" , nil ]; [actionSheet showInView:self .view]; } #pragma mark - UIActionSheetDelegate - (void )actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger )buttonIndex { if (buttonIndex == actionSheet.cancelButtonIndex) return ; UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.delegate = self ; picker.allowsEditing = YES ; picker.sourceType = buttonIndex == 1 ? UIImagePickerControllerSourceTypePhotoLibrary : UIImagePickerControllerSourceTypeCamera ; [self presentViewController:picker animated:YES completion:nil ]; } @end
The code above create a simple view with a mask, button and a place holder for the cropped image.
Where the kCropFrame
is a macro I define in pch file. This value I want to use it across multiple viewControllers.
i.e. #define kCropFrame CGRectMake(45, 80, 95, 62)
.
The question here is “How do you know the number?”
Open up the image, select the area that I wanted to crop (get the width
& height
)
Drag all the way to top left (get the position x
& y
)
Now I got the CGRect
value
Crop image controller
CropViewController.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import <UIKit /UIKit .h> @protocol CropViewControllerDelegate <NSObject >- (void )cropViewControllerDidCroppedImage:(UIImage *)image; @end @interface CropViewController : UIViewController <UIScrollViewDelegate >@property (nonatomic , weak ) id <CropViewControllerDelegate> delegate;@property (nonatomic ) UIImage *faceImage;@end
Delegate method after cropping the image
Accept the raw image from parent viewController
CropViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #import "CropViewController.h" @interface CropViewController ()@property (nonatomic ) UIImageView *maskImageView;@property (nonatomic ) UIScrollView *faceScrollView;@property (nonatomic ) UIImageView *faceImageView;@end @implementation CropViewController - (void )viewDidLoad { [super viewDidLoad]; self .view.backgroundColor = [UIColor whiteColor]; self .title = @"Crop" ; self .navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector (closeTapped:)]; self .navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStylePlain target:self action:@selector (doneTapped:)]; _faceScrollView = [[UIScrollView alloc] initWithFrame:self .view.bounds]; _faceScrollView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin ; _faceScrollView.delegate = self ; _faceScrollView.showsHorizontalScrollIndicator = NO ; _faceScrollView.showsVerticalScrollIndicator = NO ; [self .view addSubview:_faceScrollView]; _faceImageView = [[UIImageView alloc] initWithImage:_faceImage]; _faceScrollView.contentSize = _faceImageView.bounds.size; _faceScrollView.maximumZoomScale = 2 ; _faceScrollView.minimumZoomScale = _faceScrollView.frame.size.width / _faceImageView.frame.size.width;; _faceScrollView.zoomScale = _faceScrollView.minimumZoomScale; [_faceScrollView addSubview:_faceImageView]; UIView *overlayView = [[UIView alloc] initWithFrame:self .view.bounds]; overlayView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin ; overlayView.userInteractionEnabled = NO ; [self .view addSubview:overlayView]; _maskImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"sungoku" ]]; _maskImageView.center = CGPointMake (CGRectGetWidth (self .view.frame) / 2.0 , (CGRectGetHeight (self .view.frame) / 2.0 ) - 30 ); [self .view addSubview:_maskImageView]; UIBezierPath *overlayPath = [UIBezierPath bezierPathWithRect:overlayView.bounds]; UIBezierPath *transparentPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake (CGRectGetMinX (_maskImageView.frame) + CGRectGetMinX (kCropFrame), CGRectGetMinY (_maskImageView.frame) + CGRectGetMinY (kCropFrame), CGRectGetWidth (kCropFrame), CGRectGetHeight (kCropFrame))]; [overlayPath appendPath:transparentPath]; [overlayPath setUsesEvenOddFillRule:YES ]; CAShapeLayer *fillLayer = [CAShapeLayer layer]; fillLayer.path = overlayPath.CGPath; fillLayer.fillRule = kCAFillRuleEvenOdd; fillLayer.fillColor = [UIColor colorWithWhite:0 alpha:0.5 ].CGColor; [overlayView.layer addSublayer:fillLayer]; } - (void )didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - helper - (UIImage *)image:(UIImage *)image cropInRect:(CGRect )rect { CGImageRef imageRef = CGImageCreateWithImageInRect ([image CGImage ], rect); UIImage *cropped = [UIImage imageWithCGImage:imageRef]; CGImageRelease (imageRef); return cropped; } #pragma mark - event - (void )closeTapped:(id )sender { [self dismissViewControllerAnimated:YES completion:nil ]; } - (void )doneTapped:(id )sender { CGRect placeholderInGlobalSpace = [self .view convertRect:kCropFrame fromView:_maskImageView]; CGRect selectedRectInFaceImage = [self .view convertRect:placeholderInGlobalSpace toView:_faceImageView]; UIImage *croppedImage = [self image:_faceImage cropInRect:selectedRectInFaceImage]; if ([_delegate respondsToSelector:@selector (cropViewControllerDidCroppedImage:)]) { [_delegate cropViewControllerDidCroppedImage:croppedImage]; } [self dismissViewControllerAnimated:YES completion:nil ]; } #pragma mark - UIScrollViewDelegate - (UIView *)viewForZoomingInScrollView:(UIScrollView *)faceScrollView { return _faceImageView; }
Create an UIImageView
and attach it to a UIScrollView
, this is to enable the zooming
Put a mask on top of the scrollView
a) Overlay is a semi-transparent area to gray out the main image
b) Mask out the middle area (the area that we’re going to crop)
Create a helper function for cropping image
Since both the mask & the main image are not sibling, thus have to convert their position to base on the root view
After got the frame
of the mask relative to the root view, then get the frame relative to the main image view
Crop the image base on the rectangle we got just now
For zooming purpose, is a delegate method from UIScrollView
ViewController.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #import "CropViewController.h" ... @interface ViewController () <UIActionSheetDelegate , UIImagePickerControllerDelegate , UINavigationControllerDelegate , CropViewControllerDelegate >... #pragma mark - CropViewControllerDelegate - (void )cropViewControllerDidCroppedImage:(UIImage *)image { UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake (0 , 0 , image.size.width, image.size.height)]; UIGraphicsBeginImageContext (image.size); CGContextRef context = UIGraphicsGetCurrentContext (); CGContextSetFillColorWithColor (context, [UIColor colorWithPatternImage:image].CGColor); [path fill]; UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext (); UIGraphicsEndImageContext (); _cropImageView.image = bezierImage; } #pragma mark - UIImagePickerControllerDelegate - (void )imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { [self dismissViewControllerAnimated:YES completion:^{ CropViewController *controller = [[CropViewController alloc] init]; controller.delegate = self ; controller.faceImage = info[UIImagePickerControllerEditedImage ]; UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller]; [self presentViewController:navController animated:YES completion:nil ]; }]; } - (void )imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self dismissViewControllerAnimated:YES completion:nil ]; }
Conform to those protocols
The delegate method of after cropping the image. There are 2 solution here:
Solution 1 is to crop the actual image to oval shape; where Solution 2
remain the image as rectangle, but mask out the UIImageView
to display it as
oval shape. (you uncomment the line and save it to see what is the difference)
The delegate method after taking photo, make sure pass the image to the crop view controller.
Test it, you can adjust the main image.
Then the final result will be like
You can download the sample project in my GitHub repo .
References: