// // SignatureView.m // SignatureView // // Created by Michal Konturek on 05/05/2014. // Copyright (c) 2014 Michal Konturek. All rights reserved. // #import "SignatureView.h" #import #import "const.h" // //@interface SignatureView () // //@property (nonatomic, strong) NSMutableArray *drawnPoints; //@property (nonatomic, assign) CGPoint previousPoint; //@property (nonatomic, strong) UIImage *tempImage; // //@property (nonatomic, assign) BOOL blank; // //@end // //@implementation SignatureView // //- (id)initWithCoder:(NSCoder *)aDecoder { // self = [super initWithCoder:aDecoder]; // if (self) { // [self _initialize]; // } // return self; //} // //- (id)initWithFrame:(CGRect)frame { // self = [super initWithFrame:frame]; // if (self) { // [self _initialize]; // } // return self; //} // //- (void)_initialize { // // self.boundary = CGRectNull; // self.userInteractionEnabled = YES; // self.blank = YES; // // [self _setupDefaultValues]; // [self _initializeRecognizer]; //} // //- (void)_setupDefaultValues { // self.foregroundLineColor = [UIColor redColor]; // self.foregroundLineWidth = 3.0; // // self.backgroundLineColor = [UIColor blackColor]; // self.backgroundLineWidth = 3.0; //} // //- (void)_initializeRecognizer { // id recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self // action:@selector(clear)]; // self.recognizer = recognizer; // [self addGestureRecognizer:recognizer]; //} // //- (void)setLineColor:(UIColor *)color { // self.foregroundLineColor = color; // self.backgroundLineColor = color; //} // // //- (void)clear { // self.blank = YES; // self.boundary = CGRectNull; // // // [self clearWithColor:[UIColor whiteColor]]; // [self clearWithColor:[UIColor clearColor]]; //} // //- (void)clearWithColor:(UIColor *)color { // CGSize screenSize = self.frame.size; // // UIGraphicsBeginImageContext(screenSize); // // CGContextRef context = UIGraphicsGetCurrentContext(); // [self.image drawInRect:CGRectMake(0, 0, screenSize.width, screenSize.height)]; // // CGContextSetFillColorWithColor(context, color.CGColor); // // CGContextFillRect(context, CGRectMake(0, 0, screenSize.width, screenSize.height)); // if(color==[UIColor clearColor]) // CGContextClearRect(context, CGRectMake(0, 0, screenSize.width, screenSize.height)); // else // CGContextFillRect(context, CGRectMake(0, 0, screenSize.width, screenSize.height)); // // UIImage *cleanImage = UIGraphicsGetImageFromCurrentImageContext(); // self.image = cleanImage; // // UIGraphicsEndImageContext(); // // //} // //- (BOOL)isSigned { // return !self.blank; //} // //- (UIImage *)signatureImage { // // // CGRect rect = self.boundary; //// rect.size.width = rect.size.width/2; //// rect.size.height = rect.size.height/2; //// //// //// cropRect = CGRectMake ((imageHeight - imageWidth) / 2.0, 0.0, imageWidth, imageWidth); //// and //// //// // Create new cropped UIImage //// UIImage * croppedImage = [UIImage imageWithCGImage: imageRef scale: chosenImage.scale orientation: chosenImage.imageOrientation]; //// // // // rect.origin.x=rect.origin.x*self.image.scale; // rect.origin.y=rect.origin.y*self.image.scale; // rect.size.width=rect.size.width*self.image.scale; // rect.size.height=rect.size.height*self.image.scale; // //// CGImageRef imageRef = self.image.CGImage; //// CGImageRef imagePartRef = CGImageCreateWithImageInRect(imageRef, rect); //// UIImage *cropImage = [UIImage imageWithCGImage:imagePartRef scale:self.image.scale orientation:self.image.imageOrientation]; //// CGImageRelease(imagePartRef); //// return cropImage; //// //// UIImage* img= [[UIImage imageWithCGImage: CGImageCreateWithImageInRect(self.image.CGImage, rect)] copy]; // // CGImageRef cgimage = CGImageCreateWithImageInRect(self.image.CGImage, CGRectInset(rect, -10, -10)); // // UIImage* ret=[[UIImage imageWithCGImage: cgimage] copy]; // // CGImageRelease(cgimage); // return ret; // //return [self.image copy]; //} // //- (NSData *)signatureData { // // CGImageRef cgimage =CGImageCreateWithImageInRect(self.image.CGImage, CGRectInset(self.boundary, -1, -1)); // UIImage* img = [UIImage imageWithCGImage: cgimage]; // CGImageRelease(cgimage); // return UIImagePNGRepresentation(img); //} // // //#pragma mark - Touch handlers // //- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // // CGPoint currentPoint = [self _touchPointForTouches:touches]; // self.drawnPoints = [NSMutableArray arrayWithObject:[NSValue valueWithCGPoint:currentPoint]]; // self.previousPoint = currentPoint; // // /* // To be able to replace the jagged polylines with the smooth // polylines, we need to save the unmodified image. // */ // self.tempImage = self.image; //} // //- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // // CGPoint currentPoint = [self _touchPointForTouches:touches]; // [self.drawnPoints addObject:[NSValue valueWithCGPoint:currentPoint]]; // // self.image = [self _drawLineFromPoint:self.previousPoint toPoint:currentPoint image:self.image]; // self.previousPoint = currentPoint; // // self.blank = NO; //} // //- (CGPoint)_touchPointForTouches:(NSSet *)touches { // UITouch *touch = [touches anyObject]; // CGPoint point = [touch locationInView:self]; // return point; //} // //- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // // NSArray *generalizedPoints = [self _douglasPeucker:self.drawnPoints epsilon:2]; // NSArray *splinePoints = /*self.drawnPoints;//*/[self _catmullRomSpline:generalizedPoints segments:8]; // // self.image = [self _drawLineWithPoints:splinePoints image:self.tempImage]; // // // CGPoint origin = CGPointMake(MAXFLOAT, MAXFLOAT); // // CGRect boundary = CGRectNull;//CGRectMake(MAXFLOAT, MAXFLOAT, -MAXFLOAT, -MAXFLOAT); // for(int i = 0;ip.x) //// boundary.origin.x = p.x; //// if(boundary.origin.y>p.y) //// boundary.origin.y = p.y; //// //// //// if(boundary.size.height dmax) { // index = i; // dmax = d; // } // } // // // If max distance is greater than epsilon, recursively simplify // NSArray *resultList; // if(dmax > epsilon) { // NSArray *recResults1 = [self _douglasPeucker:[points subarrayWithRange:NSMakeRange(0, index + 1)] // epsilon:epsilon]; // // NSArray *recResults2 = [self _douglasPeucker:[points subarrayWithRange:NSMakeRange(index, count - index)] // epsilon:epsilon]; // // NSMutableArray *tmpList = [NSMutableArray arrayWithArray:recResults1]; // [tmpList removeLastObject]; // [tmpList addObjectsFromArray:recResults2]; // resultList = tmpList; // } else { // resultList = [NSArray arrayWithObjects:[points objectAtIndex:0], [points objectAtIndex:count - 1],nil]; // } // // return resultList; //} // //- (float)_perpendicularDistance:(CGPoint)point // lineA:(CGPoint)lineA lineB:(CGPoint)lineB { // // CGPoint v1 = CGPointMake(lineB.x - lineA.x, lineB.y - lineA.y); // CGPoint v2 = CGPointMake(point.x - lineA.x, point.y - lineA.y); // // float lenV1 = sqrt(v1.x * v1.x + v1.y * v1.y); // float lenV2 = sqrt(v2.x * v2.x + v2.y * v2.y); // float angle = acos((v1.x * v2.x + v1.y * v2.y) / (lenV1 * lenV2)); // // return sin(angle) * lenV2; //} // //- (NSArray *)_catmullRomSpline:(NSArray *)points segments:(int)segments { // // NSInteger count = [points count]; // if(count < 4) return points; // // float b[segments][4]; // { // // precompute interpolation parameters // float t = 0.0f; // float dt = 1.0f/(float)segments; // for (int i = 0; i < segments; i++, t+=dt) { // float tt = t*t; // float ttt = tt * t; // b[i][0] = 0.5f * (-ttt + 2.0f*tt - t); // b[i][1] = 0.5f * (3.0f*ttt -5.0f*tt +2.0f); // b[i][2] = 0.5f * (-3.0f*ttt + 4.0f*tt + t); // b[i][3] = 0.5f * (ttt - tt); // } // } // // NSMutableArray *resultArray = [NSMutableArray array]; // // { // int i = 0; // first control point // [resultArray addObject:[points objectAtIndex:0]]; // for (int j = 1; j < segments; j++) { // CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; // CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; // CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue]; // float px = (b[j][0]+b[j][1])*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x; // float py = (b[j][0]+b[j][1])*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y; // [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; // } // } // // for (int i = 1; i < count - 2; i++) { // // the first interpolated point is always the original control point // [resultArray addObject:[points objectAtIndex:i]]; // for (int j = 1; j < segments; j++) { // CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue]; // CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; // CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; // CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue]; // float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x; // float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y; // [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; // } // } // // { // NSInteger i = count - 2; // second to last control point // [resultArray addObject:[points objectAtIndex:i]]; // for (int j = 1; j < segments; j++) { // CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue]; // CGPoint pointI = [[points objectAtIndex:i] CGPointValue]; // CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue]; // float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + (b[j][2]+b[j][3])*pointIp1.x; // float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + (b[j][2]+b[j][3])*pointIp1.y; // [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]]; // } // } // // // the very last interpolated point is the last control point // [resultArray addObject:[points objectAtIndex:(count - 1)]]; // // return resultArray; //} // //@end //#define STROKE_WIDTH_MIN 0.004 // Stroke width determined by touch velocity //#define STROKE_WIDTH_MAX 0.010 #define STROKE_WIDTH_SMOOTHING 0.5 // Low pass filter alpha #define VELOCITY_CLAMP_MIN 20 #define VELOCITY_CLAMP_MAX 5000 #define QUADRATIC_DISTANCE_TOLERANCE 3.0 // Minimum distance to make a curve #define MAXIMUM_VERTECES 100000 static GLKVector3 StrokeColor = { 0, 0, 0 }; static float clearColor[4] = { 1, 1, 1, 0 }; // Vertex structure containing 3D point and color struct PPSSignaturePoint { GLKVector3 vertex; GLKVector3 color; }; typedef struct PPSSignaturePoint PPSSignaturePoint; // Maximum verteces in signature static const int maxLength = MAXIMUM_VERTECES; // Append vertex to array buffer static inline void addVertex(uint *length, PPSSignaturePoint v) { if ((*length) >= maxLength) { return; } GLvoid *data = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES); memcpy(data + sizeof(PPSSignaturePoint) * (*length), &v, sizeof(PPSSignaturePoint)); glUnmapBufferOES(GL_ARRAY_BUFFER); (*length)++; } static inline CGPoint QuadraticPointInCurve(CGPoint start, CGPoint end, CGPoint controlPoint, float percent) { double a = pow((1.0 - percent), 2.0); double b = 2.0 * percent * (1.0 - percent); double c = pow(percent, 2.0); return (CGPoint) { a * start.x + b * controlPoint.x + c * end.x, a * start.y + b * controlPoint.y + c * end.y }; } static float generateRandom(float from, float to) { return random() % 10000 / 10000.0 * (to - from) + from; } static float clamp(float min, float max, float value) { return fmaxf(min, fminf(max, value)); } // Find perpendicular vector from two other vectors to compute triangle strip around line static GLKVector3 perpendicular(PPSSignaturePoint p1, PPSSignaturePoint p2) { GLKVector3 ret; ret.x = p2.vertex.y - p1.vertex.y; ret.y = -1 * (p2.vertex.x - p1.vertex.x); ret.z = 0; return ret; } static PPSSignaturePoint ViewPointToGL(CGPoint viewPoint, CGRect bounds, GLKVector3 color) { return (PPSSignaturePoint) { { (viewPoint.x / bounds.size.width * 2.0 - 1), ((viewPoint.y / bounds.size.height) * 2.0 - 1) * -1, 0 }, color }; } @interface SignatureView () @property(nonatomic,assign)CGPoint mixPoint; @property(nonatomic,assign)CGPoint maxPoint; @end @interface SignatureView () { // OpenGL state EAGLContext *context; GLKBaseEffect *effect; GLuint vertexArray; GLuint vertexBuffer; GLuint dotsArray; GLuint dotsBuffer; // Array of verteces, with current length PPSSignaturePoint SignatureVertexData[maxLength]; uint length; PPSSignaturePoint SignatureDotsData[maxLength]; uint dotsLength; // Width of line at current and previous vertex float penThickness; float previousThickness; // Previous points for quadratic bezier computations CGPoint previousPoint; CGPoint previousMidPoint; PPSSignaturePoint previousVertex; PPSSignaturePoint currentVelocity; } @end @implementation SignatureView - (void)commonInit { //#define STROKE_WIDTH_MIN 0.004 // Stroke width determined by touch velocity //#define STROKE_WIDTH_MAX 0.010 self.MaxLineWidth = 0.010; self.MinLineWidth = 0.004; context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; if (context) { time(NULL); self.backgroundColor = [UIColor clearColor]; self.opaque = YES; self.context = context; self.drawableDepthFormat = GLKViewDrawableDepthFormat24; self.enableSetNeedsDisplay = YES; // Turn on antialiasing self.drawableMultisample = GLKViewDrawableMultisample4X; [self setupGL]; // Capture touches UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; pan.maximumNumberOfTouches = pan.minimumNumberOfTouches = 1; pan.cancelsTouchesInView = YES; [self addGestureRecognizer:pan]; // For dotting your i's UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; tap.cancelsTouchesInView = YES; tap.numberOfTapsRequired = 1; [self addGestureRecognizer:tap]; // Erase with long press // UILongPressGestureRecognizer *longer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; // longer.cancelsTouchesInView = YES; // [self addGestureRecognizer:longer]; } else [NSException raise:@"NSOpenGLES2ContextException" format:@"Failed to create OpenGL ES2 context"]; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) [self commonInit]; return self; } - (id)initWithFrame:(CGRect)frame context:(EAGLContext *)ctx { if (self = [super initWithFrame:frame context:ctx]) { [self commonInit]; // self.backgroundColor =[UIColor clearColor]; _mixPoint =CGPointMake(self.bounds.size.width, self.bounds.size.height); _maxPoint =CGPointMake(0, 0); // UIButton *button =[JDUtils createButtonWithFrame:CGRectMake(0,self.frame.size.height-50, self.frame.size.width/2, 50) ImageName:nil Target:self Action:@selector(okButtonClick) Title:@"确定"]; // button.backgroundColor =[UIColor colorWithWhite:0.5 alpha:0.5]; // [self addSubview:button]; // UIButton *cleanBtn = [JDUtils createButtonWithFrame:CGRectMake(SCREEN_WIDHT/2,self.frame.size.height-50, self.frame.size.width/2, 50) ImageName:nil Target:self Action:@selector(erase) Title:@"清除"]; // cleanBtn.backgroundColor =[UIColor colorWithWhite:0.5 alpha:0.5]; // [self addSubview:cleanBtn]; } return self; } //- (void)okButtonClick { // // if ([self Signature2Image]) { //// if (_okClick) { //// //// _okClick(); //// } // } // //} - (void)dealloc { [self tearDownGL]; if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } context = nil; } - (void)drawRect:(CGRect)rect { glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); glClear(GL_COLOR_BUFFER_BIT); [effect prepareToDraw]; // Drawing of signature lines if (length > 2) { glBindVertexArrayOES(vertexArray); glDrawArrays(GL_TRIANGLE_STRIP, 0, length); } if (dotsLength > 0) { glBindVertexArrayOES(dotsArray); glDrawArrays(GL_TRIANGLE_STRIP, 0, dotsLength); } } //清除画布 - (void)clear { length = 0; dotsLength = 0; self.isSigned = NO; _mixPoint =CGPointMake(self.bounds.size.width, self.bounds.size.height); _maxPoint =CGPointMake(0, 0); [self setNeedsDisplay]; } - (void)updateRectWithPoint:(CGPoint)p { if (p.x<_mixPoint.x) { _mixPoint.x =p.x; } if (p.y<_mixPoint.y) { _mixPoint.y =p.y; } if (p.x>_maxPoint.x) { _maxPoint.x =p.x; } if (p.y>_maxPoint.y) { _maxPoint.y =p.y; } } //- (BOOL)saveImage - (UIImage*)Signature2Image { if (!self.isSigned) return nil; UIImage *screenshot = [self snapshot]; if(screenshot==nil) { NSAssert(false, @"screenshot is null"); } float x = _mixPoint.x - MAX_LINEWIDTH /2.0; float y = _mixPoint.y - MAX_LINEWIDTH / 2.0; if(x<0) x=0; if(y<0) y=0; CGRect rect = CGRectMake(x*screenshot.scale, y*screenshot.scale, (_maxPoint.x-_mixPoint.x+MAX_LINEWIDTH)*screenshot.scale, (_maxPoint.y-_mixPoint.y+MAX_LINEWIDTH)*screenshot.scale); CGImageRef imageRef=screenshot.CGImage; CGImageRef imagePartRef=CGImageCreateWithImageInRect(imageRef,rect); UIImage*cropImage=[UIImage imageWithCGImage:imagePartRef]; CGImageRelease(imagePartRef);; if(cropImage==nil) { NSAssert(false, @"Signature2Image: cropImage is nil"); // int debug = 1; } return cropImage; // NSString *path =NSTemporaryDirectory(); // NSString *url = [path stringByAppendingString:@"image.png"]; // BOOL w = [UIImagePNGRepresentation(cropImage) writeToFile:url atomically:YES]; // NSLog(@"%@",path); // [self scaleImage:cropImage toScale:1.0/screenshot.scale]; // // _mixPoint =CGPointMake(self.bounds.size.width, self.bounds.size.height); // _maxPoint =CGPointMake(0, 0); // // self.strokeColor = nil; // // // // self.hidden = NO; // return w; } - (UIImage *)scaleImage:(UIImage *)image toScale:(float)scaleSize { UIGraphicsBeginImageContext(CGSizeMake(image.size.width * scaleSize, image.size.height * scaleSize)); [image drawInRect:CGRectMake(0, 0, image.size.width * scaleSize+1, image.size.height * scaleSize+1)]; UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSString *path =NSTemporaryDirectory(); NSString *url = [path stringByAppendingString:@"signImage.png"]; [UIImagePNGRepresentation(scaledImage) writeToFile:url atomically:YES]; return scaledImage; } #pragma mark - Gesture Recognizers - (void)tap:(UITapGestureRecognizer *)t { DebugLog(@"on tap"); CGPoint l = [t locationInView:self]; [self updateRectWithPoint:l]; if (t.state == UIGestureRecognizerStateRecognized) { self.isSigned = YES; DebugLog(@"UIGestureRecognizerStateRecognized"); glBindBuffer(GL_ARRAY_BUFFER, dotsBuffer); PPSSignaturePoint touchPoint = ViewPointToGL(l, self.bounds, (GLKVector3){1, 1, 1}); addVertex(&dotsLength, touchPoint); PPSSignaturePoint centerPoint = touchPoint; centerPoint.color = StrokeColor; addVertex(&dotsLength, centerPoint); static int segments = 20; GLKVector2 radius = (GLKVector2){ clamp(0.00001, 0.02, penThickness * generateRandom(0.5, 1.5)), clamp(0.00001, 0.02, penThickness * generateRandom(0.5, 1.5)) }; GLKVector2 velocityRadius = radius; float angle = 0; for (int i = 0; i <= segments; i++) { PPSSignaturePoint p = centerPoint; p.vertex.x += velocityRadius.x * cosf(angle); p.vertex.y += velocityRadius.y * sinf(angle); addVertex(&dotsLength, p); addVertex(&dotsLength, centerPoint); angle += M_PI * 2.0 / segments; } addVertex(&dotsLength, touchPoint); glBindBuffer(GL_ARRAY_BUFFER, 0); } [self setNeedsDisplay]; } //- (void)longPress:(UILongPressGestureRecognizer *)lp { // //注释regturn,可以打开长按清除 // return; // //// [self signatureImage]; // [self clear]; //} - (void)pan:(UIPanGestureRecognizer *)p { glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); CGPoint v = [p velocityInView:self]; CGPoint l = [p locationInView:self]; [self updateRectWithPoint:l]; currentVelocity = ViewPointToGL(v, self.bounds, (GLKVector3){0,0,0}); float distance = 0.; if (previousPoint.x > 0) { distance = sqrtf((l.x - previousPoint.x) * (l.x - previousPoint.x) + (l.y - previousPoint.y) * (l.y - previousPoint.y)); } float velocityMagnitude = sqrtf(v.x*v.x + v.y*v.y); float clampedVelocityMagnitude = clamp(VELOCITY_CLAMP_MIN, VELOCITY_CLAMP_MAX, velocityMagnitude); float normalizedVelocity = (clampedVelocityMagnitude - VELOCITY_CLAMP_MIN) / (VELOCITY_CLAMP_MAX - VELOCITY_CLAMP_MIN); float lowPassFilterAlpha = STROKE_WIDTH_SMOOTHING; float newThickness = (self.MaxLineWidth - self.MinLineWidth) * (1 - normalizedVelocity) + self.MinLineWidth; penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha); if ([p state] == UIGestureRecognizerStateBegan) { previousPoint = l; previousMidPoint = l; PPSSignaturePoint startPoint = ViewPointToGL(l, self.bounds, (GLKVector3){1, 1, 1}); previousVertex = startPoint; previousThickness = penThickness; addVertex(&length, startPoint); addVertex(&length, previousVertex); self.isSigned = YES; } else if ([p state] == UIGestureRecognizerStateChanged) { CGPoint mid = CGPointMake((l.x + previousPoint.x) / 2.0, (l.y + previousPoint.y) / 2.0); if (distance > QUADRATIC_DISTANCE_TOLERANCE) { // Plot quadratic bezier instead of line unsigned int i; int segments = (int) distance / 1.5; float startPenThickness = previousThickness; float endPenThickness = penThickness; previousThickness = penThickness; for (i = 0; i < segments; i++) { penThickness = startPenThickness + ((endPenThickness - startPenThickness) / segments) * i; CGPoint quadPoint = QuadraticPointInCurve(previousMidPoint, mid, previousPoint, (float)i / (float)(segments)); PPSSignaturePoint v = ViewPointToGL(quadPoint, self.bounds, StrokeColor); [self addTriangleStripPointsForPrevious:previousVertex next:v]; previousVertex = v; } } else if (distance > 1.0) { PPSSignaturePoint v = ViewPointToGL(l, self.bounds, StrokeColor); [self addTriangleStripPointsForPrevious:previousVertex next:v]; previousVertex = v; previousThickness = penThickness; } previousPoint = l; previousMidPoint = mid; } else if (p.state == UIGestureRecognizerStateEnded | p.state == UIGestureRecognizerStateCancelled) { PPSSignaturePoint v = ViewPointToGL(l, self.bounds, (GLKVector3){1, 1, 1}); addVertex(&length, v); previousVertex = v; addVertex(&length, previousVertex); } [self setNeedsDisplay]; } - (void)setStrokeColor:(UIColor *)strokeColor { _strokeColor = strokeColor; [self updateStrokeColor]; } #pragma mark - Private - (void)updateStrokeColor { CGFloat red = 0.0, green = 0.0, blue = 0.0, alpha = 0.0, white = 0.0; if (effect && self.strokeColor && [self.strokeColor getRed:&red green:&green blue:&blue alpha:&alpha]) { effect.constantColor = GLKVector4Make(red, green, blue, alpha); } else if (effect && self.strokeColor && [self.strokeColor getWhite:&white alpha:&alpha]) { effect.constantColor = GLKVector4Make(white, white, white, alpha); } else effect.constantColor = GLKVector4Make(0,0,0,1); } - (void)setBackgroundColor:(UIColor *)backgroundColor { [super setBackgroundColor:backgroundColor]; CGFloat red, green, blue, alpha, white; if ([backgroundColor getRed:&red green:&green blue:&blue alpha:&alpha]) { clearColor[0] = red; clearColor[1] = green; clearColor[2] = blue; } else if ([backgroundColor getWhite:&white alpha:&alpha]) { clearColor[0] = white; clearColor[1] = white; clearColor[2] = white; } } - (void)bindShaderAttributes { glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(PPSSignaturePoint), 0); // glEnableVertexAttribArray(GLKVertexAttribColor); // glVertexAttribPointer(GLKVertexAttribColor, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)12); } - (void)setupGL { [EAGLContext setCurrentContext:context]; effect = [[GLKBaseEffect alloc] init]; [self updateStrokeColor]; glDisable(GL_DEPTH_TEST); // Signature Lines glGenVertexArraysOES(1, &vertexArray); glBindVertexArrayOES(vertexArray); glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(SignatureVertexData), SignatureVertexData, GL_DYNAMIC_DRAW); [self bindShaderAttributes]; // Signature Dots glGenVertexArraysOES(1, &dotsArray); glBindVertexArrayOES(dotsArray); glGenBuffers(1, &dotsBuffer); glBindBuffer(GL_ARRAY_BUFFER, dotsBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(SignatureDotsData), SignatureDotsData, GL_DYNAMIC_DRAW); [self bindShaderAttributes]; glBindVertexArrayOES(0); // Perspective GLKMatrix4 ortho = GLKMatrix4MakeOrtho(-1, 1, -1, 1, 0.1f, 2.0f); effect.transform.projectionMatrix = ortho; GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -1.0f); effect.transform.modelviewMatrix = modelViewMatrix; length = 0; penThickness = 0.003; previousPoint = CGPointMake(-100, -100); } - (void)addTriangleStripPointsForPrevious:(PPSSignaturePoint)previous next:(PPSSignaturePoint)next { float toTravel = penThickness / 2.0; for (int i = 0; i < 2; i++) { GLKVector3 p = perpendicular(previous, next); GLKVector3 p1 = next.vertex; GLKVector3 ref = GLKVector3Add(p1, p); float distance = GLKVector3Distance(p1, ref); float difX = p1.x - ref.x; float difY = p1.y - ref.y; float ratio = -1.0 * (toTravel / distance); difX = difX * ratio; difY = difY * ratio; PPSSignaturePoint stripPoint = { { p1.x + difX, p1.y + difY, 0.0 }, StrokeColor }; addVertex(&length, stripPoint); toTravel *= -1; } } - (void)tearDownGL { [EAGLContext setCurrentContext:context]; glDeleteVertexArraysOES(1, &vertexArray); glDeleteBuffers(1, &vertexBuffer); glDeleteVertexArraysOES(1, &dotsArray); glDeleteBuffers(1, &dotsBuffer); effect = nil; } - (UIImage *)signatureImage { return [self Signature2Image]; // // CGRect rect = self.boundary; //// rect.size.width = rect.size.width/2; //// rect.size.height = rect.size.height/2; //// //// //// cropRect = CGRectMake ((imageHeight - imageWidth) / 2.0, 0.0, imageWidth, imageWidth); //// and //// //// // Create new cropped UIImage //// UIImage * croppedImage = [UIImage imageWithCGImage: imageRef scale: chosenImage.scale orientation: chosenImage.imageOrientation]; //// // // // rect.origin.x=rect.origin.x*self.image.scale; // rect.origin.y=rect.origin.y*self.image.scale; // rect.size.width=rect.size.width*self.image.scale; // rect.size.height=rect.size.height*self.image.scale; // //// CGImageRef imageRef = self.image.CGImage; //// CGImageRef imagePartRef = CGImageCreateWithImageInRect(imageRef, rect); //// UIImage *cropImage = [UIImage imageWithCGImage:imagePartRef scale:self.image.scale orientation:self.image.imageOrientation]; //// CGImageRelease(imagePartRef); //// return cropImage; //// //// UIImage* img= [[UIImage imageWithCGImage: CGImageCreateWithImageInRect(self.image.CGImage, rect)] copy]; // // CGImageRef cgimage = CGImageCreateWithImageInRect(self.image.CGImage, CGRectInset(rect, -10, -10)); // // UIImage* ret=[[UIImage imageWithCGImage: cgimage] copy]; // // CGImageRelease(cgimage); // return ret; //return [self.image copy]; } - (NSData *)signatureData { UIImage* signatureImage = [self Signature2Image]; // CGImageRef cgimage =CGImageCreateWithImageInRect(signatureImage.CGImage, CGRectInset(self.boundary, -1, -1)); // UIImage* img = [UIImage imageWithCGImage: cgimage]; // CGImageRelease(cgimage); return UIImagePNGRepresentation(signatureImage); } - (void)setLineWidth:(CGFloat)width { self.MaxLineWidth = width/400.0; self.MinLineWidth = width/400.0*0.4; // self.foregroundLineWidth = width; // currentVelocity = ViewPointToGL(v, self.bounds, (GLKVector3){0,0,0}); // float distance = 0.; // if (previousPoint.x > 0) { // distance = sqrtf((l.x - previousPoint.x) * (l.x - previousPoint.x) + (l.y - previousPoint.y) * (l.y - previousPoint.y)); // } // // float velocityMagnitude = sqrtf(v.x*v.x + v.y*v.y); // float clampedVelocityMagnitude = clamp(VELOCITY_CLAMP_MIN, VELOCITY_CLAMP_MAX, velocityMagnitude); // float normalizedVelocity = (clampedVelocityMagnitude - VELOCITY_CLAMP_MIN) / (VELOCITY_CLAMP_MAX - VELOCITY_CLAMP_MIN); // penThickness = (self.MaxLineWidth - self.MinLineWidth)/2.0; // float lowPassFilterAlpha = STROKE_WIDTH_SMOOTHING; // float newThickness = (self.MaxLineWidth - self.MinLineWidth) * (1 - normalizedVelocity) + self.MinLineWidth; // penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha); } #pragma mark - Touch handlers - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { } @end