Skip to content

Commit

Permalink
Added a new wrapper class around JBCroppableView called JBCroppableIm…
Browse files Browse the repository at this point in the history
…ageView. I have renamed JBCroppableView files to JBCroppableLayer so that it is less confusing. With this new UIImageView class, it is much simpler to manage setting up and manipulating crop able images. Just set up the image view as you normally would. Use methods addPoint() and removePoint() to alter the point count, and crop() and reverseCrop() to crop the image. After cropping is complete, use getCroppedImage() to get a copy of the changes. I have also changed the algorithm to make the whole thing much faster and less buggy (no more memory warnings!)
  • Loading branch information
daniel-sanche committed Dec 4, 2013
1 parent 7064842 commit fb6045b
Show file tree
Hide file tree
Showing 9 changed files with 531 additions and 551 deletions.
37 changes: 27 additions & 10 deletions Crop Demo.xcodeproj/project.pbxproj
Expand Up @@ -18,8 +18,10 @@
423E21E1168372E1001CB06C /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 423E21E0168372E1001CB06C /* Default-568h@2x.png */; };
423E21E4168372E1001CB06C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 423E21E3168372E1001CB06C /* ViewController.m */; };
423E21E7168372E1001CB06C /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 423E21E5168372E1001CB06C /* ViewController.xib */; };
423E21EF16837CA9001CB06C /* JBCroppableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 423E21EE16837CA9001CB06C /* JBCroppableView.m */; };
423E21EF16837CA9001CB06C /* JBCroppableLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 423E21EE16837CA9001CB06C /* JBCroppableLayer.m */; };
423E21F616847271001CB06C /* IMG_0152.JPG in Resources */ = {isa = PBXBuildFile; fileRef = 423E21F516847271001CB06C /* IMG_0152.JPG */; };
5FCB97A6184F1CCA0091667B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5FCB97A5184F1CCA0091667B /* QuartzCore.framework */; };
5FCB97A9184F20C90091667B /* JBCroppableImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5FCB97A8184F20C90091667B /* JBCroppableImageView.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -39,16 +41,20 @@
423E21E2168372E1001CB06C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
423E21E3168372E1001CB06C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
423E21E6168372E1001CB06C /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController.xib; sourceTree = "<group>"; };
423E21ED16837CA9001CB06C /* JBCroppableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBCroppableView.h; sourceTree = "<group>"; };
423E21EE16837CA9001CB06C /* JBCroppableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBCroppableView.m; sourceTree = "<group>"; };
423E21ED16837CA9001CB06C /* JBCroppableLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBCroppableLayer.h; sourceTree = "<group>"; };
423E21EE16837CA9001CB06C /* JBCroppableLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBCroppableLayer.m; sourceTree = "<group>"; };
423E21F516847271001CB06C /* IMG_0152.JPG */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = IMG_0152.JPG; sourceTree = "<group>"; };
5FCB97A5184F1CCA0091667B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
5FCB97A7184F20C90091667B /* JBCroppableImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JBCroppableImageView.h; sourceTree = "<group>"; };
5FCB97A8184F20C90091667B /* JBCroppableImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JBCroppableImageView.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
423E21C3168372E1001CB06C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5FCB97A6184F1CCA0091667B /* QuartzCore.framework in Frameworks */,
423E21CB168372E1001CB06C /* UIKit.framework in Frameworks */,
423E21CD168372E1001CB06C /* Foundation.framework in Frameworks */,
423E21CF168372E1001CB06C /* CoreGraphics.framework in Frameworks */,
Expand All @@ -61,6 +67,8 @@
423E21BB168372E1001CB06C = {
isa = PBXGroup;
children = (
5FCB97A7184F20C90091667B /* JBCroppableImageView.h */,
5FCB97A8184F20C90091667B /* JBCroppableImageView.m */,
423E21D0168372E1001CB06C /* TestCroping */,
423E21C9168372E1001CB06C /* Frameworks */,
423E21C7168372E1001CB06C /* Products */,
Expand All @@ -78,6 +86,7 @@
423E21C9168372E1001CB06C /* Frameworks */ = {
isa = PBXGroup;
children = (
5FCB97A5184F1CCA0091667B /* QuartzCore.framework */,
423E21CA168372E1001CB06C /* UIKit.framework */,
423E21CC168372E1001CB06C /* Foundation.framework */,
423E21CE168372E1001CB06C /* CoreGraphics.framework */,
Expand Down Expand Up @@ -117,8 +126,8 @@
423FC0C0168DB73300D17420 /* CroppableView */ = {
isa = PBXGroup;
children = (
423E21ED16837CA9001CB06C /* JBCroppableView.h */,
423E21EE16837CA9001CB06C /* JBCroppableView.m */,
423E21ED16837CA9001CB06C /* JBCroppableLayer.h */,
423E21EE16837CA9001CB06C /* JBCroppableLayer.m */,
);
name = CroppableView;
path = ../JBCroppableView;
Expand Down Expand Up @@ -152,6 +161,11 @@
attributes = {
LastUpgradeCheck = 0450;
ORGANIZATIONNAME = "Mobile one2one";
TargetAttributes = {
423E21C5168372E1001CB06C = {
DevelopmentTeam = ZY8HX93C37;
};
};
};
buildConfigurationList = 423E21C0168372E1001CB06C /* Build configuration list for PBXProject "Crop Demo" */;
compatibilityVersion = "Xcode 3.2";
Expand Down Expand Up @@ -194,7 +208,8 @@
423E21D7168372E1001CB06C /* main.m in Sources */,
423E21DB168372E1001CB06C /* AppDelegate.m in Sources */,
423E21E4168372E1001CB06C /* ViewController.m in Sources */,
423E21EF16837CA9001CB06C /* JBCroppableView.m in Sources */,
5FCB97A9184F20C90091667B /* JBCroppableImageView.m in Sources */,
423E21EF16837CA9001CB06C /* JBCroppableLayer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -272,25 +287,27 @@
423E21EB168372E1001CB06C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TestCroping/Crop Demo-Prefix.pch";
INFOPLIST_FILE = "TestCroping/Crop Demo-Info.plist";
PRODUCT_NAME = "Crop Demo";
PROVISIONING_PROFILE = "";
WRAPPER_EXTENSION = app;
};
name = Debug;
};
423E21EC168372E1001CB06C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: simon noel (7LMFUZ552E)";
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "TestCroping/Crop Demo-Prefix.pch";
INFOPLIST_FILE = "TestCroping/Crop Demo-Info.plist";
PRODUCT_NAME = "Crop Demo";
PROVISIONING_PROFILE = "";
"PROVISIONING_PROFILE[sdk=iphoneos*]" = "BCCE93CC-4AB8-44FA-BCA0-A3DC4BF66DAA";
WRAPPER_EXTENSION = app;
};
Expand Down
19 changes: 19 additions & 0 deletions JBCroppableImageView.h
@@ -0,0 +1,19 @@
//
// CropImageView.h
// Crop Demo
//
// Created by Daniel Sanche on 12/4/2013.
// Copyright (c) 2013 Mobile one2one. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface JBCroppableImageView : UIImageView

-(void)crop;
-(void)reverseCrop;
-(UIImage *)getCroppedImageWithTransparentBorders:(BOOL)transparent;
-(UIImage *)getCroppedImage;
- (void)addPoint;
- (void)removePoint;
@end
176 changes: 176 additions & 0 deletions JBCroppableImageView.m
@@ -0,0 +1,176 @@
//
// CropImageView.m
// Crop Demo
//
// Created by Daniel Sanche on 12/4/2013.
// Copyright (c) 2013 Mobile one2one. All rights reserved.
//

#import "JBCroppableImageView.h"
#import "JBCroppableLayer.h"


@implementation JBCroppableImageView{
JBCroppableLayer *_pointsView;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.userInteractionEnabled = YES;
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPoints:4];

[self addSubview:_pointsView];

UIPanGestureRecognizer *singlePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(singlePan:)];
singlePan.maximumNumberOfTouches = 1;
[self addGestureRecognizer:singlePan];

UIPanGestureRecognizer *doublePan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(doublePan:)];
doublePan.minimumNumberOfTouches = 2;
[self addGestureRecognizer:doublePan];


}
return self;
}

-(void)singlePan:(UIPanGestureRecognizer *)gesture{
CGPoint posInStretch = [gesture locationInView:_pointsView];
if(gesture.state==UIGestureRecognizerStateBegan){
[_pointsView findPointAtLocation:posInStretch];
}
if(gesture.state==UIGestureRecognizerStateEnded){
_pointsView.activePoint.backgroundColor = _pointsView.pointColor;
_pointsView.activePoint = nil;
}
[_pointsView moveActivePointToLocation:posInStretch];
// CGPoint translation = [gesture translationInView:self.imageView];
// [gesture setTranslation:CGPointZero inView:self.imageView];
}

-(void)doublePan:(UIPanGestureRecognizer *)gesture{
NSLog(@"double pan");
CGPoint translation = [gesture translationInView:self];
CGPoint newCenter = CGPointMake(self.center.x+translation.x, self.center.y+translation.y);
[self setCenter:newCenter];
[gesture setTranslation:CGPointZero inView:self];

}

-(void)crop{
[_pointsView maskImageView:self];
[_pointsView removeFromSuperview];
}

-(void)reverseCrop{
self.layer.mask = nil;
[self addSubview:_pointsView];
}

-(UIImage *)getCroppedImage{
return [_pointsView getCroppedImageForView:self withTransparentBorders:NO];
}

-(UIImage *)getCroppedImageWithTransparentBorders:(BOOL)transparent{
return [_pointsView getCroppedImageForView:self withTransparentBorders:transparent];
}

- (void)addPoint{
self.layer.mask = nil;
NSMutableArray *oldPoints = [_pointsView.getPoints mutableCopy];
// CGPoint new = CGPointMake((first.x+last.x)/2.0f, (first.y+last.y)/2.0f);

NSInteger indexOfLargestGap;
CGFloat largestGap = 0;
for(int i=0; i< oldPoints.count-1; i++){
CGPoint first = [[oldPoints objectAtIndex:i] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:i+1] CGPointValue];
CGFloat distance = [self distanceBetween:first And:last];
if(distance>largestGap){
indexOfLargestGap = i+1;
largestGap = distance;
}
}
CGPoint veryFirst = [[oldPoints firstObject] CGPointValue];
CGPoint veryLast = [[oldPoints lastObject] CGPointValue];
CGPoint new;

if([self distanceBetween:veryFirst And:veryLast]>largestGap){
indexOfLargestGap = oldPoints.count;
new = CGPointMake((veryFirst.x+veryLast.x)/2.0f, (veryFirst.y+veryLast.y)/2.0f);
} else {
CGPoint first = [[oldPoints objectAtIndex:indexOfLargestGap-1] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:indexOfLargestGap] CGPointValue];
new = CGPointMake((first.x+last.x)/2.0f, (first.y+last.y)/2.0f);
}


[oldPoints insertObject:[NSValue valueWithCGPoint:new] atIndex:indexOfLargestGap];
[_pointsView removeFromSuperview];
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPointsAt:oldPoints];
[self addSubview:_pointsView];
}

- (void)removePoint{
self.layer.mask = nil;
NSMutableArray *oldPoints = [_pointsView.getPoints mutableCopy];
if(oldPoints.count==3) return;

NSInteger indexOfSmallestGap;
CGFloat smallestGap = INFINITY;
for(int i=0; i< oldPoints.count; i++){
int firstIndex = i-1;
int lastIndex = i +1;

if(firstIndex<0){
firstIndex = (int)oldPoints.count-1+firstIndex;
} else if(firstIndex>=oldPoints.count){
firstIndex = firstIndex-(int)oldPoints.count;
}
if(lastIndex<0){
lastIndex = (int)oldPoints.count-1+lastIndex;
} else if(lastIndex>=oldPoints.count){
lastIndex = lastIndex-(int)oldPoints.count;
}

CGPoint first = [[oldPoints objectAtIndex:firstIndex] CGPointValue];
CGPoint mid = [[oldPoints objectAtIndex:i] CGPointValue];
CGPoint last = [[oldPoints objectAtIndex:lastIndex] CGPointValue];
CGFloat distance = [self distanceFrom:first to:last throuh:mid];
if(distance<smallestGap){
indexOfSmallestGap = i;
smallestGap = distance;
}
}

[oldPoints removeObjectAtIndex:indexOfSmallestGap];
[_pointsView removeFromSuperview];
_pointsView = [[JBCroppableLayer alloc] initWithImageView:self];
[_pointsView addPointsAt:[NSArray arrayWithArray:oldPoints]];
[self addSubview:_pointsView];
}

-(CGFloat)distanceBetween:(CGPoint)first And:(CGPoint)last{
CGFloat xDist = (last.x - first.x);
if(xDist<0) xDist=xDist*-1;
CGFloat yDist = (last.y - first.y);
if(yDist<0) yDist=yDist*-1;
return sqrt((xDist * xDist) + (yDist * yDist));
}
-(CGFloat)distanceFrom:(CGPoint)first to:(CGPoint)last throuh:(CGPoint)middle{
CGFloat firstToMid = [self distanceBetween:first And:middle];
CGFloat lastToMid = [self distanceBetween:middle And:last];
return firstToMid + lastToMid;
}

-(void)setFrame:(CGRect)frame{
[super setFrame:frame];
[_pointsView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[_pointsView setNeedsDisplay];
}

@end
Expand Up @@ -8,20 +8,23 @@

#import <UIKit/UIKit.h>

@interface JBCroppableView : UIView
@interface JBCroppableLayer : UIView

@property (nonatomic, strong) UIColor *pointColor;
@property (nonatomic, strong) UIColor *lineColor;

@property (nonatomic, strong) UIView *activePoint;

- (id)initWithImageView:(UIImageView *)imageView;

- (NSArray *)getPoints;
- (UIImage *)deleteBackgroundOfImage:(UIImageView *)image;
- (void)maskImageView:(UIImageView *)image;


- (void)addPointsAt:(NSArray *)points;
- (void)addPoints:(int)num;

+ (CGPoint)convertPoint:(CGPoint)point1 fromRect1:(CGSize)rect1 toRect2:(CGSize)rect2;
+ (CGRect)scaleRespectAspectFromRect1:(CGRect)rect1 toRect2:(CGRect)rect2;

-(void)findPointAtLocation:(CGPoint)location;
- (void)moveActivePointToLocation:(CGPoint)locationPoint;
-(UIImage *)getCroppedImageForView:(UIImageView *)view withTransparentBorders:(BOOL)transparent;
@end

0 comments on commit fb6045b

Please sign in to comment.