53、用Quartz和OpenGL进行绘图

Quartz与OpenGL ES绘图技术对比

用Quartz和OpenGL进行绘图

在图形绘制领域,Quartz和OpenGL ES是两种常用的技术。下面将详细介绍如何使用它们进行绘图,以及如何优化绘图应用程序。

Quartz绘图

Quartz是一种功能强大的绘图框架,可用于绘制各种图形和图像。以下是使用Quartz进行绘图的详细步骤:

  1. 定义矩形
    当使用负尺寸的 CGRect 时,它会朝着其原点的相反方向渲染(负宽度向左,负高度向上)。可以使用以下代码定义一个矩形:
    objc CGRect currentRect = CGRectMake(firstTouch.x, firstTouch.y, lastTouch.x - firstTouch.x, lastTouch.y - firstTouch.y);
  2. 绘制矩形或椭圆
    定义好矩形后,绘制矩形或椭圆就变得很简单,只需调用两个函数:一个用于在定义的 CGRect 中绘制矩形或椭圆,另一个用于描边和填充。
    objc switch (shapeType) { case kRectShape: CGContextAddRect(context, currentRect); CGContextDrawPath(context, kCGPathFillStroke); break; case kEllipseShape: CGContextAddEllipseInRect(context, currentRect); CGContextDrawPath(context, kCGPathFillStroke); break; }
  3. 绘制图像
    要绘制图像,可以将 iphone.png 图像添加到 Supporting Files 文件夹,或者使用任何你喜欢的 .png 文件,并在代码中更改文件名以指向该图像。在 drawRect: 方法中添加以下代码:
    ```objc
    • (void)drawRect:(CGRect)rect {
      CGContextRef context = UIGraphicsGetCurrentContext();
      CGContextSetLineWidth(context, 2.0);
      CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
      CGContextSetFillColorWithColor(context, currentColor.CGColor);
      CGRect currentRect = CGRectMake(firstTouch.x,
      firstTouch.y,
      lastTouch.x - firstTouch.x,
      lastTouch.y - firstTouch.y);
      switch (shapeType) {
      case kLineShape:
      CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
      CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
      CGContextStrokePath(context);
      break;
      case kRectShape:
      CGContextAddRect(context, currentRect);
      CGContextDrawPath(context, kCGPathFillStroke);
      break;
      case kEllipseShape:
      CGContextAddEllipseInRect(context, currentRect);
      CGContextDrawPath(context, kCGPathFillStroke);
      break;
      case kImageShape: {
      CGFloat horizontalOffset = drawImage.size.width / 2;
      CGFloat verticalOffset = drawImage.size.height / 2;
      CGPoint drawPoint = CGPointMake(lastTouch.x - horizontalOffset,
      lastTouch.y - verticalOffset);
      [drawImage drawAtPoint:drawPoint];
      break;
      }
      default:
      break;
      }
      }
      ```
优化QuartzFun应用程序

BIDQuartzFunView.m touchesMoved: touchesEnded: 方法中,使用 [self setNeedsDisplay]; 会导致整个视图被擦除并重新绘制,即使只有一小部分发生了变化。为了提高效率,可以使用 setNeedsDisplayInRect: 方法,该方法只会标记视图区域的一个矩形部分需要重新显示。具体操作步骤如下:

  1. BIDQuartzFunView.h 中声明 redrawRect currentRect 属性:
    objc @interface BIDQuartzFunView : UIView @property (nonatomic) CGPoint firstTouch; @property (nonatomic) CGPoint lastTouch; @property (nonatomic, strong) UIColor *currentColor; @property (nonatomic) ShapeType shapeType; @property (nonatomic, strong) UIImage *drawImage; @property (nonatomic) BOOL useRandomColor; @property (readonly) CGRect currentRect; @property CGRect redrawRect; @end
  2. BIDQuartzFunView.m 中实现 currentRect 方法:
    ```objc
    • (CGRect)currentRect {
      return CGRectMake (firstTouch.x,
      firstTouch.y,
      lastTouch.x - firstTouch.x,
      lastTouch.y - firstTouch.y);
      }
      ```
  3. drawRect: 方法中使用 self.currentRect
    ```objc
    • (void)drawRect:(CGRect)rect {
      CGContextRef context = UIGraphicsGetCurrentContext();
      CGContextSetLineWidth(context, 2.0);
      CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
      CGContextSetFillColorWithColor(context, currentColor.CGColor);
      switch (shapeType) {
      case kLineShape:
      CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
      CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
      CGContextStrokePath(context);
      break;
      case kRectShape:
      CGContextAddRect(context, self.currentRect);
      CGContextDrawPath(context, kCGPathFillStroke);
      break;
      case kEllipseShape:
      CGContextAddEllipseInRect(context, self.currentRect);
      CGContextDrawPath(context, kCGPathFillStroke);
      break;
      case kImageShape:{
      CGFloat horizontalOffset = drawImage.size.width / 2;
      CGFloat verticalOffset = drawImage.size.height / 2;
      CGPoint drawPoint = CGPointMake(lastTouch.x - horizontalOffset,
      lastTouch.y - verticalOffset);
      [drawImage drawAtPoint:drawPoint];
      break;
      }
      default:
      break;
      }
      }
      ```
  4. 替换 touchesEnded:withEvent: touchesMoved:withEvent: 方法:
    ```objc
    • (void)touchesEnded:(NSSet )touches withEvent:(UIEvent )event {
      UITouch *touch = [touches anyObject];
      lastTouch = [touch locationInView:self];
      if (shapeType == kImageShape) {
      CGFloat horizontalOffset = drawImage.size.width / 2;
      CGFloat verticalOffset = drawImage.size.height / 2;
      redrawRect = CGRectUnion(redrawRect,
      CGRectMake(lastTouch.x - horizontalOffset,
      lastTouch.y - verticalOffset,
      drawImage.size.width,
      drawImage.size.height));
      }
      else
      redrawRect = CGRectUnion(redrawRect, self.currentRect);
      redrawRect = CGRectInset(redrawRect, -2.0, -2.0);
      [self setNeedsDisplayInRect:redrawRect];
      }
    • (void)touchesMoved:(NSSet )touches withEvent:(UIEvent )event {
      UITouch *touch = [touches anyObject];
      lastTouch = [touch locationInView:self];
      if (shapeType == kImageShape) {
      CGFloat horizontalOffset = drawImage.size.width / 2;
      CGFloat verticalOffset = drawImage.size.height / 2;
      redrawRect = CGRectUnion(redrawRect,
      CGRectMake(lastTouch.x - horizontalOffset,
      lastTouch.y - verticalOffset,
      drawImage.size.width,
      drawImage.size.height));
      }
      redrawRect = CGRectUnion(redrawRect, self.currentRect);
      [self setNeedsDisplayInRect:redrawRect];
      }
      ```
OpenGL ES绘图

OpenGL ES和Quartz在绘图方面采用了根本不同的方法。下面将使用OpenGL ES重新创建Quartz应用程序,以让你了解其基础知识和一些示例代码。

设置GLFun应用程序
  1. 关闭QuartzFun Xcode项目,在Finder中复制项目文件夹并将其重命名为GLFun。
  2. 双击文件夹打开,再双击 QuartzFun.xcodeproj 打开文件。
  3. 在项目导航器顶部单击 QuartzFun ,等待约一秒后再次单击,将其重命名为GLFun并按回车键确认。
  4. 点击 Rename 按钮重命名项目的所有组件部分。
  5. 重命名包含源代码文件的 QuartzFun 组为GLFun。
  6. 16 - GLFun 文件夹中添加 Texture2D.h Texture2D.m OpenGLES2DView.h OpenGLES2DView.m 四个文件到项目中。
  7. 删除 BIDQuartzFunView.h BIDQuartzFunView.m 文件。
创建BIDGLFunView
  1. 选择GLFun文件夹并按 ⌘N 创建新文件,选择 Objective-C class 并点击 Next
  2. 将类命名为 BIDGLFunView 并使其成为 OpenGLES2DView 的子类。
  3. 点击 Next 并将文件保存到项目文件夹中。
  4. 替换 BIDGLFunView.h 的内容:
    objc #import "BIDConstants.h" #import "OpenGLES2DView.h" @class Texture2D; @interface BIDGLFunView : OpenGLES2DView @property CGPoint firstTouch; @property CGPoint lastTouch; @property (nonatomic, strong) UIColor *currentColor; @property BOOL useRandomColor; @property ShapeType shapeType; @property (nonatomic, strong) Texture2D *sprite; @end
  5. BIDGLFunView.m 中添加以下代码:
    ```objc
    #import “BIDGLFunView.h”
    #import “UIColor+BIDRandom.h”
    #import “Texture2D.h”
    @implementation BIDGLFunView
    @synthesize firstTouch;
    @synthesize lastTouch;
    @synthesize currentColor;
    @synthesize useRandomColor;
    @synthesize shapeType;
    @synthesize sprite;
    • (id)initWithCoder:(NSCoder*)coder {
      if (self = [super initWithCoder:coder]) {
      self.currentColor = [UIColor redColor];
      useRandomColor = NO;
      sprite = [[Texture2D alloc] initWithImage:[UIImage
      imageNamed:@”iphone.png”]];
      glBindTexture(GL_TEXTURE_2D, sprite.name);
      }
      return self;
      }
    • (void)draw {
      glLoadIdentity();
      glClearColor(0.78f, 0.78f, 0.78f, 1.0f);
      glClear(GL_COLOR_BUFFER_BIT);
      CGColorRef color = currentColor.CGColor;
      const CGFloat *components = CGColorGetComponents(color);
      CGFloat red = components[0];
      CGFloat green = components[1];
      CGFloat blue = components[2];
      glColor4f(red,green, blue, 1.0);
      switch (shapeType) {
      case kLineShape: {
      glDisable(GL_TEXTURE_2D);
      GLfloat vertices[4];
      vertices[0] = firstTouch.x;
      vertices[1] = self.frame.size.height - firstTouch.y;
      vertices[2] = lastTouch.x;
      vertices[3] = self.frame.size.height - lastTouch.y;
      glLineWidth(2.0);
      glVertexPointer(2, GL_FLOAT, 0, vertices);
      glDrawArrays(GL_LINES, 0, 2);
      break;
      }
      case kRectShape: {
      glDisable(GL_TEXTURE_2D);
      GLfloat vertices[8];
      GLfloat minX = (firstTouch.x > lastTouch.x) ?
      lastTouch.x : firstTouch.x;
      GLfloat minY = (self.frame.size.height - firstTouch.y >
      self.frame.size.height - lastTouch.y) ?
      self.frame.size.height - lastTouch.y :
      self.frame.size.height - firstTouch.y;
      GLfloat maxX = (firstTouch.x > lastTouch.x) ?
      firstTouch.x : lastTouch.x;
      GLfloat maxY = (self.frame.size.height - firstTouch.y >
      self.frame.size.height - lastTouch.y) ?
      self.frame.size.height - firstTouch.y :
      self.frame.size.height - lastTouch.y;
      vertices[0] = maxX;
      vertices[1] = maxY;
      vertices[2] = minX;
      vertices[3] = maxY;
      vertices[4] = minX;
      vertices[5] = minY;
      vertices[6] = maxX;
      vertices[7] = minY;
      glVertexPointer(2, GL_FLOAT , 0, vertices);
      glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
      break;
      }
      case kEllipseShape: {
      glDisable(GL_TEXTURE_2D);
      GLfloat vertices[720];
      GLfloat xradius = fabsf((firstTouch.x - lastTouch.x) / 2);
      GLfloat yradius = fabsf((firstTouch.y - lastTouch.y) / 2);
      for (int i = 0; i <= 720; i += 2) {
      GLfloat xOffset = (firstTouch.x > lastTouch.x) ?
      lastTouch.x + xradius : firstTouch.x + xradius;
      GLfloat yOffset = (firstTouch.y < lastTouch.y) ?
      self.frame.size.height - lastTouch.y + yradius :
      self.frame.size.height - firstTouch.y + yradius;
      vertices[i] = (cos(degreesToRadian(i / 2)) * xradius) + xOffset;
      vertices[i+1] = (sin(degreesToRadian(i / 2)) * yradius) +
      yOffset;
      }
      glVertexPointer(2, GL_FLOAT , 0, vertices);
      glDrawArrays(GL_TRIANGLE_FAN, 0, 360);
      break;
      }
      case kImageShape:
      glEnable(GL_TEXTURE_2D);
      [sprite drawAtPoint:CGPointMake(lastTouch.x,
      self.frame.size.height - lastTouch.y)];
      break;
      default:
      break;
      }
      glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
      [context presentRenderbuffer:GL_RENDERBUFFER_OES];
      }
    • (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event {
      if (useRandomColor)
      self.currentColor = [UIColor randomColor];
      UITouch* touch = [[event touchesForView:self] anyObject];
      firstTouch = [touch locationInView:self];
      lastTouch = [touch locationInView:self];
      [self draw];
      }
    • (void)touchesMoved:(NSSet )touches withEvent:(UIEvent )event {
      UITouch *touch = [touches anyObject];
      lastTouch = [touch locationInView:self];
      [self draw];
      }
    • (void)touchesEnded:(NSSet )touches withEvent:(UIEvent )event {
      UITouch *touch = [touches anyObject];
      lastTouch = [touch locationInView:self];
      [self draw];
      }
      @end
      ```
Quartz和OpenGL ES绘图比较
绘图方式 优点 缺点
Quartz 代码简洁,易于使用,有许多内置的绘图函数 性能相对较低,不适合复杂的图形和动画
OpenGL ES 性能高,支持硬件加速,适合复杂的图形和动画 代码复杂,需要更多的编程知识和经验
OpenGL ES绘图流程
graph TD;
    A[开始绘图] --> B[绘制图形];
    B --> C[渲染上下文到缓冲区];
    C --> D[显示渲染缓冲区];
    D --> E[绘图结束];

通过以上步骤,你可以使用Quartz和OpenGL ES进行绘图,并优化绘图应用程序的性能。希望这些内容对你有所帮助!

用Quartz和OpenGL进行绘图(续)

深入理解OpenGL ES绘图细节
绘制直线

在OpenGL ES中绘制直线,需要进行一系列的操作。首先,要重置虚拟世界,清除背景,设置绘图颜色,关闭纹理映射功能。然后,将 CGPoint 结构体转换为顶点数组,指定线宽,传递数组给OpenGL ES并绘制。最后,渲染缓冲区并显示。以下是详细的步骤和代码解释:

  1. 重置虚拟世界
    objc glLoadIdentity();
    这行代码用于重置虚拟世界,清除之前可能应用的旋转、平移或其他变换。

  2. 清除背景
    objc glClearColor(0.78f, 0.78f, 0.78f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
    这里将背景颜色设置为特定的灰色,并清除颜色缓冲区。

  3. 设置绘图颜色
    objc CGColorRef color = currentColor.CGColor; const CGFloat *components = CGColorGetComponents(color); CGFloat red = components[0]; CGFloat green = components[1]; CGFloat blue = components[2]; glColor4f(red, green, blue, 1.0);
    UIColor 中提取RGB分量,并设置OpenGL ES的绘图颜色。

  4. 关闭纹理映射
    objc glDisable(GL_TEXTURE_2D);
    关闭纹理映射功能,确保绘制的颜色能够显示出来。

  5. 定义顶点数组
    objc GLfloat vertices[4]; vertices[0] = firstTouch.x; vertices[1] = self.frame.size.height - firstTouch.y; vertices[2] = lastTouch.x; vertices[3] = self.frame.size.height - lastTouch.y;
    将两个 CGPoint 结构体转换为顶点数组,同时注意y坐标的转换。

  6. 指定线宽并绘制
    objc glLineWidth(2.0); glVertexPointer(2, GL_FLOAT, 0, vertices); glDrawArrays(GL_LINES, 0, 2);
    设置线宽为2.0,传递顶点数组给OpenGL ES,并绘制直线。

  7. 渲染并显示缓冲区
    objc glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES];
    绑定渲染缓冲区并显示。

绘制椭圆

OpenGL ES没有直接绘制椭圆的函数,需要手动定义顶点数组来模拟椭圆。具体步骤如下:

  1. 重置和清除
    objc glLoadIdentity(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_TEXTURE_2D);
    重置虚拟世界,清除背景为白色,关闭纹理映射。

  2. 设置绘图颜色
    objc CGColorRef color = currentColor.CGColor; const CGFloat *components = CGColorGetComponents(color); CGFloat red = components[0]; CGFloat green = components[1]; CGFloat blue = components[2]; glColor4f(red, green, blue, 1.0);
    提取RGB分量并设置绘图颜色。

  3. 定义顶点数组
    objc GLfloat vertices[720]; GLfloat xradius = fabsf((firstTouch.x - lastTouch.x) / 2); GLfloat yradius = fabsf((firstTouch.y - lastTouch.y) / 2); for (int i = 0; i <= 720; i += 2) { GLfloat xOffset = (firstTouch.x > lastTouch.x) ? lastTouch.x + xradius : firstTouch.x + xradius; GLfloat yOffset = (firstTouch.y < lastTouch.y) ? self.frame.size.height - lastTouch.y + yradius : self.frame.size.height - firstTouch.y + yradius; vertices[i] = (cos(degreesToRadian(i / 2)) * xradius) + xOffset; vertices[i+1] = (sin(degreesToRadian(i / 2)) * yradius) + yOffset; }
    计算椭圆的水平和垂直半径,通过循环计算椭圆上每个点的坐标,并存储在顶点数组中。

  4. 绘制和渲染
    objc glVertexPointer(2, GL_FLOAT, 0, vertices); glDrawArrays(GL_TRIANGLE_FAN, 0, 360); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES];
    传递顶点数组给OpenGL ES,绘制椭圆,然后渲染并显示缓冲区。

绘制矩形

绘制矩形的步骤与绘制直线和椭圆类似,主要是定义矩形的四个顶点并传递给OpenGL ES进行绘制。代码如下:

glDisable(GL_TEXTURE_2D);
GLfloat vertices[8];
GLfloat minX = (firstTouch.x > lastTouch.x) ?
lastTouch.x : firstTouch.x;
GLfloat minY = (self.frame.size.height - firstTouch.y >
                self.frame.size.height - lastTouch.y) ?
self.frame.size.height - lastTouch.y : 
self.frame.size.height - firstTouch.y;
GLfloat maxX = (firstTouch.x > lastTouch.x) ?
firstTouch.x : lastTouch.x;
GLfloat maxY = (self.frame.size.height - firstTouch.y >
                self.frame.size.height - lastTouch.y) ?
self.frame.size.height - firstTouch.y : 
self.frame.size.height - lastTouch.y;
vertices[0] = maxX;
vertices[1] = maxY;
vertices[2] = minX;
vertices[3] = maxY;
vertices[4] = minX;
vertices[5] = minY;
vertices[6] = maxX;
vertices[7] = minY;
glVertexPointer(2, GL_FLOAT, 0, vertices);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
绘制图像

使用 Texture2D 类绘制图像相对简单,但需要确保启用纹理映射功能。代码如下:

glEnable(GL_TEXTURE_2D);
[sprite drawAtPoint:CGPointMake(lastTouch.x,
                                self.frame.size.height - lastTouch.y)];
触摸事件处理在OpenGL ES中的差异

在OpenGL ES版本中,触摸事件处理方法与之前的版本有所不同。不再需要告诉视图哪些部分需要更新,而是直接调用 draw 方法。因为OpenGL ES会自动计算需要更新的部分,并利用硬件加速以最有效的方式进行绘制。以下是触摸事件处理方法的代码:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (useRandomColor)
        self.currentColor = [UIColor randomColor];
    UITouch* touch = [[event touchesForView:self] anyObject];
    firstTouch = [touch locationInView:self];
    lastTouch = [touch locationInView:self];
    [self draw];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    lastTouch = [touch locationInView:self];
    [self draw];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    lastTouch = [touch locationInView:self];
    [self draw];
}
总结与建议

通过对Quartz和OpenGL ES绘图的学习,我们可以看到它们各有优缺点。Quartz适合简单的绘图任务,代码简洁易读;而OpenGL ES则更适合复杂的图形和动画,性能更高,但代码复杂度也更高。

以下是一些使用建议:
- 简单绘图 :如果只是进行简单的图形绘制,如绘制直线、矩形、椭圆等,Quartz是一个不错的选择。它的内置函数可以让你快速实现绘图功能。
- 复杂图形和动画 :对于需要高性能和硬件加速的复杂图形和动画,OpenGL ES是更好的选择。虽然学习曲线较陡,但一旦掌握,能够实现更出色的效果。
- 性能优化 :在使用Quartz时,要注意避免不必要的全屏重绘,可以使用 setNeedsDisplayInRect: 方法提高效率。而在使用OpenGL ES时,要充分利用其硬件加速功能,合理组织顶点数组和绘制操作。

希望通过本文的介绍,你对Quartz和OpenGL ES绘图有了更深入的理解,并能够根据实际需求选择合适的绘图方式。

相关操作步骤总结

操作类型 操作步骤
Quartz绘图 1. 定义矩形;2. 绘制矩形或椭圆;3. 绘制图像
优化QuartzFun应用程序 1. 声明属性;2. 实现 currentRect 方法;3. 使用 self.currentRect ;4. 替换触摸事件方法
设置GLFun应用程序 1. 复制并重命名项目;2. 打开项目并重命名;3. 重命名组件;4. 添加文件;5. 删除旧文件
创建BIDGLFunView 1. 创建新文件;2. 命名并设置子类;3. 保存文件;4. 替换 BIDGLFunView.h 内容;5. 添加 BIDGLFunView.m 代码

OpenGL ES绘图详细流程

graph TD;
    A[开始绘图请求] --> B[重置虚拟世界];
    B --> C[清除背景];
    C --> D[设置绘图颜色];
    D --> E{选择图形类型};
    E -- 直线 --> F[定义直线顶点数组];
    E -- 矩形 --> G[定义矩形顶点数组];
    E -- 椭圆 --> H[定义椭圆顶点数组];
    E -- 图像 --> I[启用纹理映射];
    F --> J[指定线宽并绘制];
    G --> J;
    H --> J;
    I --> K[绘制图像];
    J --> L[渲染上下文到缓冲区];
    K --> L;
    L --> M[显示渲染缓冲区];
    M --> N[绘图结束];

通过以上总结和流程图,你可以更清晰地了解Quartz和OpenGL ES绘图的操作步骤和流程。在实际应用中,根据具体需求选择合适的绘图方式,并进行相应的优化,以达到最佳的绘图效果和性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值