iOS:用openGLES实现可自由选择各种颜色,宽度,可绘制曲线,矩形,圆形等各种形状,带橡皮擦,可撤回操作的画板涂鸦工具<一>
上边主要提了用openGLES画图的基本操作,其中有一个细节,就是在绘制圆形、矩形、直线时,会去调用showlines,也就是重新绘制之前的
if (model.penType == kDrawPenTypeCurve || model.isEraser) {
[self renderLineFromPoint:CGPointMake(lastPointModel.x, lastPointModel.y) toPoint:CGPointMake(pointModel.x, pointModel.y)];
}else{
[self showLines];
}
这里原因是圆形和矩形这些在绘制过程中需要在绘制的过程中去消除绘制痕迹,所以,这里直接去重绘,在绘制图案不复杂的时候,是没问题的。那如果绘制的图案很复杂,每次重绘就会很耗费性能,甚至会有卡顿的感觉。
iOS:可自由选择各种颜色,宽度,带橡皮擦,可撤回操作的画板涂鸦工具
这篇用iOS系统api画图里,对于这种就会比较好处理,就是把每一笔绘制的图案都缓存下来,这样在绘制圆、矩形时,直接使用保存的image就行,这种也同样适用于对撤销的处理上。
但如果是openGLES的话,就不能直接简单地适用了。
首先是获取当前绘制的image不能简单用self.image来获取了。
这里重新写一个image的获取方法:
- (UIImage*)image
{
glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
GLint x = 0, y = 0, width = _backingWidth, height = _backingHeight;
NSInteger dataLength = width * height * 4;
GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,ref, NULL, true, kCGRenderingIntentDefault);
NSInteger widthInPoints, heightInPoints;
CGFloat scale = self.contentScaleFactor;
widthInPoints = width / scale;
heightInPoints = height / scale;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);
CGContextRef cgcontext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(cgcontext, kCGBlendModeCopy);
CGContextDrawImage(cgcontext, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
free(data);
CFRelease(ref);
CFRelease(colorspace);
CGImageRelease(iref);
return image;
}
获取的问题解决了,另一个问题就是,怎么把缓存图片绘制到视图上去
- (void)renderImage
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, _backingWidth, _backingHeight);
static BOOL hasShader = NO;
static GLuint position;
static GLuint textCoor;
static GLuint rotate ;
if (!hasShader) {
//读取文件路径
NSString* vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
NSString* fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
//加载shader
self.myProgram = [self loadShaders:vertFile frag:fragFile];
//链接
glLinkProgram(self.myProgram);
GLint linkSuccess;
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) { //连接错误
GLchar messages[256];
glGetProgramInfoLog(self.myProgram, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"error%@", messageString);
return ;
}
else {
NSLog(@"link ok");
}
position = glGetAttribLocation(self.myProgram, "position");
textCoor = glGetAttribLocation(self.myProgram, "textCoordinate");
//获取shader里面的变量,这里记得要在glLinkProgram后面,后面,后面!
rotate = glGetUniformLocation(self.myProgram, "rotateMatrix");
}
glUseProgram(self.myProgram);
//前三个是顶点坐标, 后面两个是纹理坐标
GLfloat attrArr[] =
{
1.0f, -1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, -1.0f, 0.0f, 0.0f,
1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 0.0f, 1.0f,
1.0f, -1.0f, -1.0f, 1.0f, 0.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float *)NULL + 3);
glEnableVertexAttribArray(position);
glEnableVertexAttribArray(textCoor);
//加载纹理
if (![self setupTexture:((UIImage *)(self.imageBuffersArray.lastObject)).CGImage]) {
return;
}
//z轴旋转矩阵
GLfloat zRotation[16] = {
1.0,0,0,0,
0,-1.0,0,0,
0,0,1.0,0,
0,0,0,1.0
};
//设置旋转矩阵
glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);
glDrawArrays(GL_TRIANGLES, 0, 6);
hasShader = YES;
}
这样,在touchesBegan时去缓存当前绘制的图片
UIImage * currentImage = self.image;
if (currentImage && self.pointsArray.count) {
[self.imageBuffersArray addObject:currentImage];
}
if (self.imageBuffersArray.count > self.imageBufferMaxCount) {
[self.imageBuffersArray removeObjectAtIndex:0];
}
然后在touchesMoved方法里去使用缓存图片
if (model.penType == kDrawPenTypeCurve || model.isEraser) {
[self renderWithType:kDrawPenTypeCurve fromPoint:CGPointMake(lastPointModel.x, lastPointModel.y) toPoint:CGPointMake(pointModel.x, pointModel.y)];
}else{
lastPointModel = model.pointsArray.firstObject;
[EAGLContext setCurrentContext:self.context];
glBindFramebuffer(GL_FRAMEBUFFER, _viewFramebuffer);
[self renderImage];
[self render];
[self readyRenderWithType:model.penType fromPoint:CGPointMake(lastPointModel.x, lastPointModel.y) toPoint:CGPointMake(pointModel.x, pointModel.y)];
glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
同样,在撤销方法里,也可以使用
- (void)undo
{
if (!self.pointsArray.count) return;
[self.pointsArray removeLastObject];
if (self.imageBuffersArray.count) {
[self renderImage];
[self render];
[self.context presentRenderbuffer:GL_RENDERBUFFER];
[self.imageBuffersArray removeLastObject];
}else{
[self showLines];
}
}
ps:之前有尝试过用橡皮擦模式来实现撤销以及抹除圆形矩形绘制轨迹这种功能