iOS:用openGLES实现可自由选择各种颜色,宽度,可绘制曲线,矩形,圆形等各种形状,带橡皮擦,可撤回操作的画板涂鸦工具<一>

用openGLES实现一个画板功能,除了能实现实时涂鸦之外,还要实现动态改变线条颜色,线条宽度,自由绘制圆形矩形等形状,还要实现橡皮擦,撤回上一步操作,清理画板等功能...
图片是以前截的:
iOS:可自由选择各种颜色,宽度,带橡皮擦,可撤回操作的画板涂鸦工具

image

这里是提供几种常用颜色供选择,此外还提供了颜色选择器,可自由选择线条颜色
自由选择线条颜色

首先是初始化openGL
设置layer

- (void)setupLayer{
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

// CALayer 默认是透明的,必须将它设为不透明才能让其可见
eaglLayer.opaque = YES;

// 设置描绘属性,在这里设置不维持渲染内容以及颜色格式为 RGBA8
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
//设置放大倍数
[self setContentScaleFactor:[[UIScreen mainScreen] scale]];}

设置上下文

- (void)setupContext{
// 指定 OpenGL 渲染 API 的版本,在这里我们使用 OpenGL ES 2.0
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

 // 设置为当前上下文
if (!_context || ![EAGLContext setCurrentContext:_context]) {
    
    NSLog(@"Failed to initialize OpenGLES 2.0 context");
    
    exit(1);
}}

设置缓冲区

- (void)setupBuffer{
glDeleteFramebuffers(1, &_viewFramebuffer);

_viewRenderbuffer = 0;

glDeleteRenderbuffers(1, &_viewRenderbuffer);

_viewRenderbuffer = 0;

glGenFramebuffers(1, &_viewFramebuffer);

glGenRenderbuffers(1, &_viewRenderbuffer);

// 设置为当前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _viewFramebuffer);

glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);

// 为 颜色缓冲区 分配存储空间
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];

// 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _viewRenderbuffer);

glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);

glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);}

渲染

 - (BOOL)render{
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
    NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
    return NO;
}
// Setup the view port in Pixels
glViewport(0, 0, _backingWidth, _backingHeight);

// Create a Vertex Buffer Object to hold our data
glGenBuffers(1, &_vboId);

// Load the brush texture
_brushTexture = [self textureFromName:@"Particle.png"];

// Load shaders
[self setupShaders];

// Enable blending and set a blending function appropriate for premultiplied alpha pixel data
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

return YES;}

设置着色器

- (void)setupShaders{
for (int i = 0; i < NUM_PROGRAMS; i++)
{
    char *vsrc = readFile(pathForResource(program[i].vert));
    char *fsrc = readFile(pathForResource(program[i].frag));
    GLsizei attribCt = 0;
    GLchar *attribUsed[NUM_ATTRIBS];
    GLint attrib[NUM_ATTRIBS];
    GLchar *attribName[NUM_ATTRIBS] = {
        "inVertex",
    };
    const GLchar *uniformName[NUM_UNIFORMS] = {
        "MVP", "pointSize", "vertexColor", "texture",
    };
    
    // auto-assign known attribs
    for (int j = 0; j < NUM_ATTRIBS; j++)
    {
        if (strstr(vsrc, attribName[j]))
        {
            attrib[attribCt] = j;
            attribUsed[attribCt++] = attribName[j];
        }
    }
    
    glueCreateProgram(vsrc, fsrc,
                      attribCt, (const GLchar **)&attribUsed[0], attrib,
                      NUM_UNIFORMS, &uniformName[0], program[i].uniform,
                      &program[i].id);
    free(vsrc);
    free(fsrc);
    
    // Set constant/initalize uniforms
    if (i == PROGRAM_POINT)
    {
        glUseProgram(program[PROGRAM_POINT].id);
        
        // the brush texture will be bound to texture unit 0
        glUniform1i(program[PROGRAM_POINT].uniform[UNIFORM_TEXTURE], 0);
        
        // viewing matrices
        GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, _backingWidth, 0, _backingHeight, -1, 1);
        GLKMatrix4 modelViewMatrix = GLKMatrix4Identity; // this sample uses a constant identity modelView matrix
        GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
        
        glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
    
        // point size
        glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], _brushTexture.width / 12 * self.lineWidthBlock());
        
        // initialize brush color
        glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, _brushColor);
    }
}

glError();}

设置纹理

- (textureInfo_t)textureFromName:(NSString *)name{
CGImageRef      brushImage;
CGContextRef    brushContext;
GLubyte         *brushData;
size_t          width, height;
GLuint          texId;
textureInfo_t   texture;

// 1获取图片的CGImageRef
brushImage = [UIImage imageNamed:name].CGImage;

// 2 读取图片的大小
width = CGImageGetWidth(brushImage);

height = CGImageGetHeight(brushImage);

// Allocate  memory needed for the bitmap context
brushData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
// Use  the bitmatp creation function provided by the Core Graphics framework.
brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
// 3在CGContextRef上绘图
CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), brushImage);
// You don't need the context at this point, so you need to release it to avoid memory leaks.
CGContextRelease(brushContext);
// Use OpenGL ES to generate a name for the texture.
glGenTextures(1, &texId);
// 4绑定纹理到默认的纹理ID(这里只有一张图片,故而相当于默认于片元着色器里面的colorMap,如果有多张图不可以这么做)
glBindTexture(GL_TEXTURE_2D, texId);
// Set the texture parameters to use a minifying filter and a linear filer (weighted average)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Specify a 2D texture image, providing the a pointer to the image data in memory
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
// Release  the image data; it's no longer needed
free(brushData);

texture.id = texId;
texture.width = (int)width;
texture.height = (int)height;

return texture;}

画线部分代码

  - (NSInteger)readyRenderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end{
static GLfloat*        vertexBuffer = NULL;
static NSUInteger    vertexMax = 64;
NSUInteger            vertexCount = 0,
count,
i;

// Convert locations from Points to Pixels
CGFloat scale = self.contentScaleFactor;
start.x *= scale;
start.y *= scale;
end.x *= scale;
end.y *= scale;

// Allocate vertex array buffer
if(vertexBuffer == NULL)
    vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));

// Add points to the buffer so there are drawing points every X pixels
count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1);
for(i = 0; i < count; ++i) {
    if(vertexCount == vertexMax) {
        vertexMax = 2 * vertexMax;
        vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
    }
    
    vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i / (GLfloat)count);
    vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i / (GLfloat)count);
    vertexCount += 1;
}

// Load data to the Vertex Buffer Object
glBindBuffer(GL_ARRAY_BUFFER, _vboId);
glBufferData(GL_ARRAY_BUFFER, vertexCount*2*sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(ATTRIB_VERTEX);
glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, 0);

// Draw
glUseProgram(program[PROGRAM_POINT].id);
glDrawArrays(GL_POINTS, 0, (int)vertexCount);

return vertexCount;}

矩形绘制就是四条直线:

- (NSInteger)readyRenderRectangleFromPoint:(CGPoint)start toPoint:(CGPoint)end{
CGPoint point2 = CGPointMake(start.x, end.y);

CGPoint point3 = CGPointMake(end.x, start.y);

NSInteger totalCount = 0;

totalCount += [self readyRenderLineFromPoint:start toPoint:point2];

totalCount += [self readyRenderLineFromPoint:point2 toPoint:end];

totalCount += [self readyRenderLineFromPoint:end toPoint:point3];

totalCount += [self readyRenderLineFromPoint:point3 toPoint:start];

return totalCount;}

圆形的绘制将圆切割成100条线去绘制:

- (NSInteger)readyRenderCircularFromPoint:(CGPoint)start toPoint:(CGPoint)end{
GLfloat radius = sqrt(pow((start.x - end.x), 2) + pow((start.y - end.y), 2))/2;     //半径

CGPoint center = CGPointMake((start.x + end.x)/2, (start.y + end.y)/2);             //圆心

GLfloat pointCount = 100;

GLfloat delta = 2.0*M_PI/pointCount;

NSString * lastPoint = nil;

CGPoint firstPoint = CGPointZero;

CGFloat totalCount = 0;

for (NSInteger i = 0; i < pointCount; i++) {
    
    GLfloat x = radius * cos(delta * i) + center.x;
    
    GLfloat y = radius * sin(delta * i) + center.y;
    
    CGPoint currentPoint = CGPointMake(x, y);
    
    if (i == 0) {
        
        firstPoint = currentPoint;
    }
    
    if (lastPoint != nil) {
        
        totalCount += [self readyRenderLineFromPoint:CGPointFromString(lastPoint) toPoint:currentPoint];
    }
    
    lastPoint = NSStringFromCGPoint(currentPoint);
    
    if (i == pointCount - 1) {
        
        totalCount += [self readyRenderLineFromPoint:CGPointFromString(lastPoint) toPoint:firstPoint];
    }
}

return totalCount;}

绘制的时候则是根据每次操作的画笔类型去绘制:

- (void)showLines{
[EAGLContext setCurrentContext:_context];

glBindFramebuffer(GL_FRAMEBUFFER, _viewFramebuffer);

glClearColor(0.0, 0.0, 0.0, 0.0);

glClear(GL_COLOR_BUFFER_BIT);

NSInteger totalCount = 0;

for (NSInteger i = 0; i < self.pointsArray.count; i++) {
    
    DrawModel * model = self.pointsArray[i];
    
    UIColor * currentColor = model.lineColor;
    
    [self setBrushColorWithRed:[currentColor.RGBArray[0] floatValue] green:[currentColor.RGBArray[1] floatValue] blue:[currentColor.RGBArray[2] floatValue] alpha:[currentColor.RGBArray[3] floatValue]];
    
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    
    // point size
    glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], _brushTexture.width / 12 * model.lineWidth);
    
    if (model.isEraser) {
        
        [self setIsEraser:model.isEraser];
    }
    
    if (model.pointsArray.count >= 2) {
        
        if (model.penType == kDrawPenTypeCurve || model.isEraser) {
            
            for (NSInteger j = 0; j < model.pointsArray.count - 1; j++) {
                
                DrawPointModel * pointModel1 = model.pointsArray[j];
                
                DrawPointModel * pointModel2 = model.pointsArray[j + 1];
                
                CGPoint start = CGPointMake(pointModel1.x, pointModel1.y);
                
                CGPoint end = CGPointMake(pointModel2.x, pointModel2.y);
                
                totalCount += [self readyRenderLineFromPoint:start toPoint:end];
            }
            
        }else{
            
            DrawPointModel * pointModel1 = model.pointsArray.firstObject;
            
            DrawPointModel * pointModel2 = model.pointsArray.lastObject;
            
            CGPoint start = CGPointMake(pointModel1.x, pointModel1.y);
            
            CGPoint end = CGPointMake(pointModel2.x, pointModel2.y);
            
            switch (model.penType) {
                    
                case kDrawPenTypeStraight:
                {
                    totalCount += [self readyRenderLineFromPoint:start toPoint:end];
                }
                    break;
                case kDrawPenTypeRectangle:
                {
                    totalCount += [self readyRenderRectangleFromPoint:start toPoint:end];
                }
                    break;
                case kDrawPenTypeCircular:
                {
                    totalCount += [self readyRenderCircularFromPoint:start toPoint:end];
                }
                    break;
                    
                default:
                    break;
            }
        }
    }
}

// Display the buffer
glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);

[_context presentRenderbuffer:GL_RENDERBUFFER];

NSLog(@"totalCount: %zd",totalCount);}

然后是撤销,则是将上次操作的所有point移除,并重新绘制,此处的showLines就是将所有点重新绘制一遍

- (void)undo{
if (!self.pointsArray.count) return;

[self.pointsArray removeLastObject];

[self showLines];}

橡皮擦则是修改下颜色以及绘制模式

   - (void)setIsEraser:(BOOL)isEraser{
if (isEraser) {
    
    [self setBrushColorWithRed:0 green:0 blue:0 alpha:0];
    
    // 设置绘画模式
    glBlendFunc(GL_ONE, GL_ZERO);
    
}else{
    
    UIColor * currentColor = self.lineColorBlock();
    
    [self setBrushColorWithRed:[currentColor.RGBArray[0] floatValue] green:[currentColor.RGBArray[1] floatValue] blue:[currentColor.RGBArray[2] floatValue] alpha:[currentColor.RGBArray[3] floatValue]];
    
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}}

效果图如下:


多种形状的绘制

具体操作逻辑细节和UI等是参考微信的图片编辑中的画板

Github代码地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容