diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fa00987 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +# EditorConfig: http://editorconfig.org + +[*.{m,h}] +indent_style = space +indent_size = 4 diff --git a/Source/PPSSignatureView.m b/Source/PPSSignatureView.m index 9ad6d1e..3083ef8 100644 --- a/Source/PPSSignatureView.m +++ b/Source/PPSSignatureView.m @@ -1,6 +1,8 @@ #import "PPSSignatureView.h" #import +#define ERROR_OPENGL 1 + #define STROKE_WIDTH_MIN 0.004 // Stroke width determined by touch velocity #define STROKE_WIDTH_MAX 0.030 #define STROKE_WIDTH_SMOOTHING 0.5 // Low pass filter alpha @@ -28,25 +30,51 @@ // Maximum verteces in signature static const int maxLength = MAXIMUM_VERTECES; +static inline GLvoid *mapVertexBuffer(GLuint bufferToMap, NSError **error) { + glBindBuffer(GL_ARRAY_BUFFER, bufferToMap); + + GLvoid *data = glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES); + + if (data == NULL && error != NULL) { + GLenum glError = glGetError(); + + NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; + userInfo[@"GL_ERROR"] = @(glError); + + *error = [NSError errorWithDomain:@"PPSSignatureView" code:ERROR_OPENGL userInfo:userInfo]; + } + + return data; +} // Append vertex to array buffer -static inline void addVertex(uint *length, PPSSignaturePoint v) { +static inline void addVertex(GLvoid *mappedBuffer, uint *length, PPSSignaturePoint vertex) { 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); - + + memcpy(mappedBuffer + sizeof(PPSSignaturePoint) * (*length), &vertex, sizeof(PPSSignaturePoint)); (*length)++; } +static inline void unmapVertexBuffer(GLvoid *mappedBuffer) { + if (mappedBuffer != NULL) { + GLboolean result = glUnmapBufferOES(GL_ARRAY_BUFFER); + + if (result == GL_FALSE) { + // GL docs say this indicates some kind of corruption, and the buffer should be reinitialized. + // TODO(rgrimm): Reinitialize the buffer + } + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + 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 @@ -83,26 +111,26 @@ @interface PPSSignatureView () { // OpenGL state EAGLContext *context; GLKBaseEffect *effect; - + GLuint vertexArray; GLuint vertexBuffer; GLuint dotsArray; GLuint dotsBuffer; - - - // Array of verteces, with current length + + + // Array of vertices, 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; @@ -118,42 +146,41 @@ @implementation PPSSignatureView - (void)commonInit { context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - + if (context) { time(NULL); - + self.backgroundColor = [UIColor whiteColor]; self.opaque = NO; - + 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; [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]; @@ -171,7 +198,7 @@ - (id)initWithFrame:(CGRect)frame context:(EAGLContext *)ctx - (void)dealloc { [self tearDownGL]; - + if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } @@ -185,7 +212,7 @@ - (void)drawRect:(CGRect)rect glClear(GL_COLOR_BUFFER_BIT); [effect prepareToDraw]; - + // Drawing of signature lines if (length > 2) { glBindVertexArrayOES(vertexArray); @@ -203,12 +230,10 @@ - (void)erase { length = 0; dotsLength = 0; self.hasSignature = NO; - + [self setNeedsDisplay]; } - - - (UIImage *)signatureImage { if (!self.hasSignature) @@ -219,7 +244,7 @@ - (UIImage *)signatureImage // self.strokeColor = [UIColor whiteColor]; // [self setNeedsDisplay]; UIImage *screenshot = [self snapshot]; - + // self.strokeColor = nil; // // self.hidden = NO; @@ -232,42 +257,47 @@ - (UIImage *)signatureImage - (void)tap:(UITapGestureRecognizer *)t { CGPoint l = [t locationInView:self]; - + if (t.state == 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; + NSError *error; + GLvoid *mappedBuffer = mapVertexBuffer(dotsBuffer, &error); + + if (mappedBuffer == NULL) { + // TODO(rgrimm): Handle the error condition + } else { + PPSSignaturePoint touchPoint = ViewPointToGL(l, self.bounds, (GLKVector3) {1, 1, 1}); + addVertex(mappedBuffer, &dotsLength, touchPoint); + + PPSSignaturePoint centerPoint = touchPoint; + centerPoint.color = StrokeColor; + addVertex(mappedBuffer, &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(mappedBuffer, &dotsLength, p); + addVertex(mappedBuffer, &dotsLength, centerPoint); + + angle += M_PI * 2.0 / segments; + } + + addVertex(mappedBuffer, &dotsLength, touchPoint); } - - addVertex(&dotsLength, touchPoint); - - glBindBuffer(GL_ARRAY_BUFFER, 0); + + unmapVertexBuffer(mappedBuffer); } - + [self setNeedsDisplay]; } @@ -277,86 +307,93 @@ - (void)longPress:(UILongPressGestureRecognizer *)lp { } - (void)pan:(UIPanGestureRecognizer *)p { - - glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); - - CGPoint v = [p velocityInView:self]; - CGPoint l = [p locationInView:self]; - - 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 = (STROKE_WIDTH_MAX - STROKE_WIDTH_MIN) * (1 - normalizedVelocity) + STROKE_WIDTH_MIN; - 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.hasSignature = 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; + + NSError *error; + GLvoid *mappedBuffer = mapVertexBuffer(vertexBuffer, &error); + + if (mappedBuffer == NULL) { + // TODO(rgrimm): Handle the error condition + } else { + + CGPoint velocity = [p velocityInView:self]; + CGPoint location = [p locationInView:self]; + + currentVelocity = ViewPointToGL(velocity, self.bounds, (GLKVector3) {0, 0, 0}); + float distance = 0.; + if (previousPoint.x > 0) { + distance = sqrtf((location.x - previousPoint.x) * (location.x - previousPoint.x) + (location.y - previousPoint.y) * (location.y - previousPoint.y)); + } + + float velocityMagnitude = sqrtf(velocity.x * velocity.x + velocity.y * velocity.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 = (STROKE_WIDTH_MAX - STROKE_WIDTH_MIN) * (1 - normalizedVelocity) + STROKE_WIDTH_MIN; + penThickness = penThickness * lowPassFilterAlpha + newThickness * (1 - lowPassFilterAlpha); + + if ([p state] == UIGestureRecognizerStateBegan) { + + previousPoint = location; + previousMidPoint = location; + + PPSSignaturePoint startPoint = ViewPointToGL(location, self.bounds, (GLKVector3) {1, 1, 1}); + previousVertex = startPoint; 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; + + addVertex(mappedBuffer, &length, startPoint); + addVertex(mappedBuffer, &length, previousVertex); + + self.hasSignature = YES; + + } else if ([p state] == UIGestureRecognizerStateChanged) { + + CGPoint mid = CGPointMake((location.x + previousPoint.x) / 2.0, (location.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 vertex = ViewPointToGL(quadPoint, self.bounds, StrokeColor); + [self addTriangleStripPointsInMappedBuffer:mappedBuffer previous:previousVertex next:vertex]; + + previousVertex = vertex; + } + } else if (distance > 1.0) { + + PPSSignaturePoint vertex = ViewPointToGL(location, self.bounds, StrokeColor); + [self addTriangleStripPointsInMappedBuffer:mappedBuffer previous:previousVertex next:vertex]; + + previousVertex = vertex; + previousThickness = penThickness; } - } else if (distance > 1.0) { - - PPSSignaturePoint v = ViewPointToGL(l, self.bounds, StrokeColor); - [self addTriangleStripPointsForPrevious:previousVertex next:v]; - - previousVertex = v; - previousThickness = penThickness; + + previousPoint = location; + previousMidPoint = mid; + + } else if (p.state == UIGestureRecognizerStateEnded | p.state == UIGestureRecognizerStateCancelled) { + + PPSSignaturePoint vertex = ViewPointToGL(location, self.bounds, (GLKVector3) {1, 1, 1}); + addVertex(mappedBuffer, &length, vertex); + + previousVertex = vertex; + addVertex(mappedBuffer, &length, previousVertex); } - - 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); } - + + unmapVertexBuffer(mappedBuffer); + [self setNeedsDisplay]; } @@ -381,7 +418,7 @@ - (void)updateStrokeColor { - (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; @@ -404,73 +441,71 @@ - (void)bindShaderAttributes { - (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 { +- (void)addTriangleStripPointsInMappedBuffer:(GLvoid *)mappedBuffer previous:(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); - + addVertex(mappedBuffer, &length, stripPoint); + toTravel *= -1; } } @@ -479,13 +514,13 @@ - (void)addTriangleStripPointsForPrevious:(PPSSignaturePoint)previous next:(PPSS - (void)tearDownGL { [EAGLContext setCurrentContext:context]; - + glDeleteVertexArraysOES(1, &vertexArray); glDeleteBuffers(1, &vertexBuffer); - + glDeleteVertexArraysOES(1, &dotsArray); glDeleteBuffers(1, &dotsBuffer); - + effect = nil; }