手写签字并保存图片
网上找了点现成的代码,又自己改了下...(前人方法)
首先新建一个PPSSignatureView继承GLKView
然后再使用的时候,创建一个继承GLKViewController的控制器,这里是关键,避免入坑,UIView addSubView的时候, glkView是无法显示的...
简单说,代码如下:
.h文件
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@interface PPSSignatureView : GLKView
@property (nonatomic, strong) GLKBaseEffect *effect;
@property (nonatomic, assign) CGFloat colorR;
@property (nonatomic, assign) CGFloat colorG;
@property (nonatomic, assign) CGFloat colorB;
@property (nonatomic, assign) CGFloat alpha;
@property (nonatomic, assign) float maxFontSize;
@property (assign, nonatomic) UIColor *strokeColor;
@property (assign, nonatomic) BOOL hasSignature;
@property (strong, nonatomic) UIImage *signatureImage;
@property (nonatomic, strong) NSMutableArray *pointMArray;
/** 清除所有线条*/
- (void)erase;
@end
.m文件
#import "PPSSignatureView.h"
#import <OpenGLES/ES2/glext.h>
#define STROKE_WIDTH_MIN 0.004 // Stroke width determined by touch velocity
#define STROKE_WIDTH_SMOOTHING 0.3 // 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 PPSSignatureView () {
// OpenGL state
EAGLContext *context;
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;
}
@property (nonatomic, strong) NSMutableArray *drawPointArray;
@end
@implementation PPSSignatureView
- (NSMutableArray *)pointMArray {
if(nil == _pointMArray) {
_pointMArray = [NSMutableArray array];
}
return _pointMArray;
}
- (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];
#warning 这个地方是长按删除的操作
// 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];
return self;
}
- (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);
[self.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);
}
}
#warning 清除的操作
- (void)erase {
length = 0;
dotsLength = 0;
self.hasSignature = NO;
[self setNeedsDisplay];
}
- (UIImage *)signatureImage
{
if (!self.hasSignature)
return nil;
// self.hidden = YES;
//
// self.strokeColor = [UIColor whiteColor];
// [self setNeedsDisplay];
UIImage *screenshot = [self snapshot];
// self.strokeColor = nil;
//
// self.hidden = NO;
return screenshot;
}
#pragma mark - Gesture Recognizers
- (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;
}
addVertex(&dotsLength, touchPoint);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
[self setNeedsDisplay];
}
- (void)longPress:(UILongPressGestureRecognizer *)lp {
[self erase];
}
- (void)pan:(UIPanGestureRecognizer *)p {
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
CGPoint v = [p velocityInView:self];
CGPoint l = [p locationInView:self];
//[self.drawPointArray addObject:];
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.maxFontSize - 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;
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 {
self.effect.constantColor = GLKVector4Make(_colorR, _colorG, _colorB, _alpha);
}
- (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];
self.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);
self.effect.transform.projectionMatrix = ortho;
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -1.0f);
self.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);
self.effect = nil;
}
@end