Skip to content

Add method to flip image horizontally (around vertical axis) #567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ NS_ASSUME_NONNULL_BEGIN
/// Crops a portion of an existing image object and returns it as a new image
/// @param frame The region inside the image (In image pixel space) to crop
/// @param angle If any, the angle the image is rotated at as well
/// @param flipped If image should be flipped horizontally
/// @param circular Whether the resulting image is returned as a square or a circle
- (nonnull UIImage *)croppedImageWithFrame:(CGRect)frame
angle:(NSInteger)angle
flippedHorizontally:(BOOL)flipped
circularClip:(BOOL)circular;

@end
Expand Down
10 changes: 8 additions & 2 deletions Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ - (BOOL)hasAlpha
alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaPremultipliedLast);
}

- (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circularClip:(BOOL)circular
- (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle flippedHorizontally:(BOOL)flipped circularClip:(BOOL)circular
{
UIImage *croppedImage = nil;
UIGraphicsBeginImageContextWithOptions(frame.size, !self.hasAlpha && !circular, self.scale);
Expand Down Expand Up @@ -63,7 +63,13 @@ - (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circular
// Perform the rotation transformation
CGContextRotateCTM(context, rotation);
}


if (flipped)
{
CGContextScaleCTM(context, -1, 1);
CGContextTranslateCTM(context, -self.size.width, 0);
}

// Draw the image with all of the transformation parameters applied.
// We do not need to worry about specifying the size here since we're already
// constrained by the context image size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonnull, nonatomic, readonly) UIImage *image;
@property (nonatomic, readonly) CGRect cropFrame;
@property (nonatomic, readonly) NSInteger angle;
@property (nonatomic, readonly) BOOL flippedHorizontally;
@property (nonatomic, readonly) BOOL circular;

- (nonnull instancetype)initWithImage:(nonnull UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular;
- (nonnull instancetype)initWithImage:(nonnull UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle flipped:(BOOL)isFlipped circular:(BOOL)circular;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ @interface TOActivityCroppedImageProvider ()
@property (nonatomic, strong, readwrite) UIImage *image;
@property (nonatomic, assign, readwrite) CGRect cropFrame;
@property (nonatomic, assign, readwrite) NSInteger angle;
@property (nonatomic, assign, readwrite) BOOL flippedHorizontally;
@property (nonatomic, assign, readwrite) BOOL circular;

@property (atomic, strong) UIImage *croppedImage;
Expand All @@ -36,12 +37,13 @@ @interface TOActivityCroppedImageProvider ()

@implementation TOActivityCroppedImageProvider

- (instancetype)initWithImage:(UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular
- (instancetype)initWithImage:(UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle flipped:(BOOL)isFlipped circular:(BOOL)circular
{
if (self = [super initWithPlaceholderItem:[UIImage new]]) {
_image = image;
_cropFrame = cropFrame;
_angle = angle;
_flippedHorizontally = isFlipped;
_circular = circular;
}

Expand All @@ -68,7 +70,7 @@ - (id)item
return self.croppedImage;
}

UIImage *image = [self.image croppedImageWithFrame:self.cropFrame angle:self.angle circularClip:self.circular];
UIImage *image = [self.image croppedImageWithFrame:self.cropFrame angle:self.angle flippedHorizontally:self.flippedHorizontally circularClip:self.circular];
self.croppedImage = image;
return self.croppedImage;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ NS_ASSUME_NONNULL_BEGIN
@interface TOCroppedImageAttributes : NSObject

@property (nonatomic, readonly) NSInteger angle;
@property (nonatomic, readonly) BOOL isFlippedHorizontally;
@property (nonatomic, readonly) CGRect croppedFrame;
@property (nonatomic, readonly) CGSize originalImageSize;

- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize;
- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle flippedHorizontally:(BOOL)flipped originalImageSize:(CGSize)originalSize;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,20 @@
@interface TOCroppedImageAttributes ()

@property (nonatomic, assign, readwrite) NSInteger angle;
@property (nonatomic, assign, readwrite) BOOL isFlippedHorizontally;
@property (nonatomic, assign, readwrite) CGRect croppedFrame;
@property (nonatomic, assign, readwrite) CGSize originalImageSize;

@end

@implementation TOCroppedImageAttributes

- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize
- (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle
flippedHorizontally:(BOOL)flipped originalImageSize:(CGSize)originalSize
{
if (self = [super init]) {
_angle = angle;
_isFlippedHorizontally = flipped;
_croppedFrame = croppedFrame;
_originalImageSize = originalSize;
}
Expand Down
32 changes: 25 additions & 7 deletions Objective-C/TOCropViewController/TOCropViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
*/
- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
didCropImageToRect:(CGRect)cropRect
angle:(NSInteger)angle;
angle:(NSInteger)angle
flipped:(BOOL)flipped;

/**
Called when the user has committed the crop action, and provides
Expand All @@ -56,7 +57,8 @@
*/
- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
didCropToImage:(nonnull UIImage *)image withRect:(CGRect)cropRect
angle:(NSInteger)angle;
angle:(NSInteger)angle
flipped:(BOOL)flipped;

/**
If the cropping style is set to circular, implementing this delegate will return a circle-cropped version of the selected
Expand All @@ -68,7 +70,8 @@
*/
- (void)cropViewController:(nonnull TOCropViewController *)cropViewController
didCropToCircularImage:(nonnull UIImage *)image withRect:(CGRect)cropRect
angle:(NSInteger)angle;
angle:(NSInteger)angle
flipped:(BOOL)flipped;

/**
If implemented, when the user hits cancel, or completes a
Expand Down Expand Up @@ -131,6 +134,14 @@
*/
@property (nonatomic, assign) NSInteger angle;

/**
Indicates if the image is flipped around vertical axis

This property can be set before the controller is presented to have
the image 'restored' to a previous cropping layout.
*/
@property (nonatomic, assign) BOOL flippedHorizontally;

/**
The toolbar view managed by this view controller.
*/
Expand Down Expand Up @@ -327,7 +338,7 @@
(In the original image's local co-ordinate space)
@param angle The angle of the image when it was cropped
*/
@property (nullable, nonatomic, strong) void (^onDidCropImageToRect)(CGRect cropRect, NSInteger angle);
@property (nullable, nonatomic, strong) void (^onDidCropImageToRect)(CGRect cropRect, NSInteger angle, BOOL flipped);

/**
Called when the user has committed the crop action, and provides
Expand All @@ -338,7 +349,7 @@
(In the original image's local co-ordinate space)
@param angle The angle of the image when it was cropped
*/
@property (nullable, nonatomic, strong) void (^onDidCropToRect)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle);
@property (nullable, nonatomic, strong) void (^onDidCropToRect)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle, BOOL flipped);

/**
If the cropping style is set to circular, this block will return a circle-cropped version of the selected
Expand All @@ -349,7 +360,7 @@
(In the original image's local co-ordinate space)
@param angle The angle of the image when it was cropped
*/
@property (nullable, nonatomic, strong) void (^onDidCropToCircleImage)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle);
@property (nullable, nonatomic, strong) void (^onDidCropToCircleImage)(UIImage* _Nonnull image, CGRect cropRect, NSInteger angle, BOOL flipped);


///------------------------------------------------
Expand Down Expand Up @@ -390,6 +401,11 @@
*/
- (void)setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)aspectRatioPreset animated:(BOOL)animated NS_SWIFT_NAME(setAspectRatioPreset(_:animated:));

/**
Flips image around vertical axis as if user pressed flip button in the upper right corner themself
*/
- (void)flipImageHorizontally;

/**
Play a custom animation of the target image zooming to its position in
the crop controller while the background fades in.
Expand Down Expand Up @@ -418,6 +434,7 @@
@param fromView A view that's frame will be used as the origin for this animation. Optional if `fromFrame` has a value.
@param fromFrame In the screen's coordinate space, the frame from which the image should animate from.
@param angle The rotation angle in which the image was rotated when it was originally cropped.
@param flipped Was the image flipped on the x axis.
@param toFrame In the image's coordinate space, the previous crop frame that created the previous crop
@param setup A block that is called just before the transition starts. Recommended for hiding any necessary image views.
@param completion A block that is called once the transition animation is completed.
Expand All @@ -427,9 +444,10 @@
fromView:(nullable UIView *)fromView
fromFrame:(CGRect)fromFrame
angle:(NSInteger)angle
flippedHorizontally:(BOOL)flipped
toImageFrame:(CGRect)toFrame
setup:(nullable void (^)(void))setup
completion:(nullable void (^)(void))completion NS_SWIFT_NAME(presentAnimatedFrom(_:fromImage:fromView:fromFrame:angle:toFrame:setup:completion:));
completion:(nullable void (^)(void))completion NS_SWIFT_NAME(presentAnimatedFrom(_:fromImage:fromView:fromFrame:angle:flippedHorizontally:toFrame:setup:completion:));

/**
Play a custom animation of the supplied cropped image zooming out from
Expand Down
50 changes: 34 additions & 16 deletions Objective-C/TOCropViewController/TOCropViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,11 @@ - (void)setAspectRatioPreset:(TOCropViewControllerAspectRatioPreset)aspectRatioP
[self.cropView setAspectRatio:aspectRatio animated:animated];
}

- (void)flipImageHorizontally
{
[self.cropView flipImageHorizontally];
}

- (void)rotateCropViewClockwise
{
[self.cropView rotateImageNinetyDegreesAnimated:YES clockwise:YES];
Expand Down Expand Up @@ -713,14 +718,15 @@ - (void)presentAnimatedFromParentViewController:(UIViewController *)viewControll
completion:(void (^)(void))completion
{
[self presentAnimatedFromParentViewController:viewController fromImage:nil fromView:fromView fromFrame:fromFrame
angle:0 toImageFrame:CGRectZero setup:setup completion:completion];
angle:0 flippedHorizontally:NO toImageFrame:CGRectZero setup:setup completion:completion];
}

- (void)presentAnimatedFromParentViewController:(UIViewController *)viewController
fromImage:(UIImage *)image
fromView:(UIView *)fromView
fromFrame:(CGRect)fromFrame
angle:(NSInteger)angle
flippedHorizontally:(BOOL)flipped
toImageFrame:(CGRect)toFrame
setup:(void (^)(void))setup
completion:(void (^)(void))completion
Expand All @@ -734,6 +740,7 @@ - (void)presentAnimatedFromParentViewController:(UIViewController *)viewControll
self.angle = angle;
self.imageCropFrame = toFrame;
}
self.flippedHorizontally = flipped;

__weak typeof (self) weakSelf = self;
[viewController presentViewController:self.parentViewController ? self.parentViewController : self
Expand Down Expand Up @@ -904,12 +911,13 @@ - (void)dismissCropViewController
- (void)doneButtonTapped
{
CGRect cropFrame = self.cropView.imageCropFrame;
NSInteger angle = self.cropView.angle;
NSInteger angle = self.angle;
BOOL flipped = self.flippedHorizontally;

//If desired, when the user taps done, show an activity sheet
if (self.showActivitySheetOnDone) {
TOActivityCroppedImageProvider *imageItem = [[TOActivityCroppedImageProvider alloc] initWithImage:self.image cropFrame:cropFrame angle:angle circular:(self.croppingStyle == TOCropViewCroppingStyleCircular)];
TOCroppedImageAttributes *attributes = [[TOCroppedImageAttributes alloc] initWithCroppedFrame:cropFrame angle:angle originalImageSize:self.image.size];
TOActivityCroppedImageProvider *imageItem = [[TOActivityCroppedImageProvider alloc] initWithImage:self.image cropFrame:cropFrame angle:angle flipped:flipped circular:(self.croppingStyle == TOCropViewCroppingStyleCircular)];
TOCroppedImageAttributes *attributes = [[TOCroppedImageAttributes alloc] initWithCroppedFrame:cropFrame angle:angle flippedHorizontally:flipped originalImageSize:self.image.size];

NSMutableArray *activityItems = [@[imageItem, attributes] mutableCopy];
if (self.activityItems) {
Expand Down Expand Up @@ -961,35 +969,35 @@ - (void)doneButtonTapped
BOOL isCallbackOrDelegateHandled = NO;

//If the delegate/block that only supplies crop data is provided, call it
if ([self.delegate respondsToSelector:@selector(cropViewController:didCropImageToRect:angle:)]) {
[self.delegate cropViewController:self didCropImageToRect:cropFrame angle:angle];
if ([self.delegate respondsToSelector:@selector(cropViewController:didCropImageToRect:angle:flipped:)]) {
[self.delegate cropViewController:self didCropImageToRect:cropFrame angle:angle flipped:flipped];
isCallbackOrDelegateHandled = YES;
}

if (self.onDidCropImageToRect != nil) {
self.onDidCropImageToRect(cropFrame, angle);
self.onDidCropImageToRect(cropFrame, angle, flipped);
isCallbackOrDelegateHandled = YES;
}

// Check if the circular APIs were implemented
BOOL isCircularImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToCircularImage:withRect:angle:)];
BOOL isCircularImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToCircularImage:withRect:angle:flipped:)];
BOOL isCircularImageCallbackAvailable = self.onDidCropToCircleImage != nil;

// Check if non-circular was implemented
BOOL isDidCropToImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToImage:withRect:angle:)];
BOOL isDidCropToImageDelegateAvailable = [self.delegate respondsToSelector:@selector(cropViewController:didCropToImage:withRect:angle:flipped:)];
BOOL isDidCropToImageCallbackAvailable = self.onDidCropToRect != nil;

//If cropping circular and the circular generation delegate/block is implemented, call it
if (self.croppingStyle == TOCropViewCroppingStyleCircular && (isCircularImageDelegateAvailable || isCircularImageCallbackAvailable)) {
UIImage *image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:YES];
UIImage *image = [self.image croppedImageWithFrame:cropFrame angle:angle flippedHorizontally:flipped circularClip:YES];

//Dispatch on the next run-loop so the animation isn't interuppted by the crop operation
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (isCircularImageDelegateAvailable) {
[self.delegate cropViewController:self didCropToCircularImage:image withRect:cropFrame angle:angle];
[self.delegate cropViewController:self didCropToCircularImage:image withRect:cropFrame angle:angle flipped:flipped];
}
if (isCircularImageCallbackAvailable) {
self.onDidCropToCircleImage(image, cropFrame, angle);
self.onDidCropToCircleImage(image, cropFrame, angle, flipped);
}
});

Expand All @@ -998,21 +1006,21 @@ - (void)doneButtonTapped
//If the delegate/block that requires the specific cropped image is provided, call it
else if (isDidCropToImageDelegateAvailable || isDidCropToImageCallbackAvailable) {
UIImage *image = nil;
if (angle == 0 && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size})) {
if (angle == 0 && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size}) && !flipped) {
image = self.image;
}
else {
image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:NO];
image = [self.image croppedImageWithFrame:cropFrame angle:angle flippedHorizontally:flipped circularClip:NO];
}

//Dispatch on the next run-loop so the animation isn't interuppted by the crop operation
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (isDidCropToImageDelegateAvailable) {
[self.delegate cropViewController:self didCropToImage:image withRect:cropFrame angle:angle];
[self.delegate cropViewController:self didCropToImage:image withRect:cropFrame angle:angle flipped:flipped];
}

if (isDidCropToImageCallbackAvailable) {
self.onDidCropToRect(image, cropFrame, angle);
self.onDidCropToRect(image, cropFrame, angle, flipped);
}
});

Expand Down Expand Up @@ -1215,6 +1223,16 @@ - (NSInteger)angle
return self.cropView.angle;
}

- (void)setFlippedHorizontally:(BOOL)flipped
{
self.cropView.flippedHorizontally = flipped;
}

- (BOOL)flippedHorizontally
{
return self.cropView.flippedHorizontally;
}

- (void)setImageCropFrame:(CGRect)imageCropFrame
{
self.cropView.imageCropFrame = imageCropFrame;
Expand Down
7 changes: 7 additions & 0 deletions Objective-C/TOCropViewController/Views/TOCropView.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) NSInteger angle;

@property (nonatomic, assign) BOOL flippedHorizontally;

/**
Hide all of the crop elements for transition animations
*/
Expand Down Expand Up @@ -291,6 +293,11 @@ The minimum croping aspect ratio. If set, user is prevented from setting croppin
*/
- (void)moveCroppedContentToCenterAnimated:(BOOL)animated;

/**
Flips image around vertical axis
*/
- (void)flipImageHorizontally;

@end

NS_ASSUME_NONNULL_END
Loading