用Quartz和OpenGL进行绘图
在图形绘制领域,Quartz和OpenGL ES是两种常用的技术。下面将详细介绍如何使用它们进行绘图,以及如何优化绘图应用程序。
Quartz绘图
Quartz是一种功能强大的绘图框架,可用于绘制各种图形和图像。以下是使用Quartz进行绘图的详细步骤:
-
定义矩形
:
当使用负尺寸的CGRect时,它会朝着其原点的相反方向渲染(负宽度向左,负高度向上)。可以使用以下代码定义一个矩形:
objc CGRect currentRect = CGRectMake(firstTouch.x, firstTouch.y, lastTouch.x - firstTouch.x, lastTouch.y - firstTouch.y); -
绘制矩形或椭圆
:
定义好矩形后,绘制矩形或椭圆就变得很简单,只需调用两个函数:一个用于在定义的CGRect中绘制矩形或椭圆,另一个用于描边和填充。
objc switch (shapeType) { case kRectShape: CGContextAddRect(context, currentRect); CGContextDrawPath(context, kCGPathFillStroke); break; case kEllipseShape: CGContextAddEllipseInRect(context, currentRect); CGContextDrawPath(context, kCGPathFillStroke); break; } -
绘制图像
:
要绘制图像,可以将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;
}
}
```
-
(void)drawRect:(CGRect)rect {
优化QuartzFun应用程序
在
BIDQuartzFunView.m
的
touchesMoved:
和
touchesEnded:
方法中,使用
[self setNeedsDisplay];
会导致整个视图被擦除并重新绘制,即使只有一小部分发生了变化。为了提高效率,可以使用
setNeedsDisplayInRect:
方法,该方法只会标记视图区域的一个矩形部分需要重新显示。具体操作步骤如下:
-
在
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 -
在
BIDQuartzFunView.m中实现currentRect方法:
```objc-
(CGRect)currentRect {
return CGRectMake (firstTouch.x,
firstTouch.y,
lastTouch.x - firstTouch.x,
lastTouch.y - firstTouch.y);
}
```
-
(CGRect)currentRect {
-
在
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;
}
}
```
-
(void)drawRect:(CGRect)rect {
-
替换
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];
}
```
-
(void)touchesEnded:(NSSet
)touches withEvent:(UIEvent
)event {
OpenGL ES绘图
OpenGL ES和Quartz在绘图方面采用了根本不同的方法。下面将使用OpenGL ES重新创建Quartz应用程序,以让你了解其基础知识和一些示例代码。
设置GLFun应用程序
- 关闭QuartzFun Xcode项目,在Finder中复制项目文件夹并将其重命名为GLFun。
-
双击文件夹打开,再双击
QuartzFun.xcodeproj打开文件。 -
在项目导航器顶部单击
QuartzFun,等待约一秒后再次单击,将其重命名为GLFun并按回车键确认。 -
点击
Rename按钮重命名项目的所有组件部分。 -
重命名包含源代码文件的
QuartzFun组为GLFun。 -
从
16 - GLFun文件夹中添加Texture2D.h、Texture2D.m、OpenGLES2DView.h和OpenGLES2DView.m四个文件到项目中。 -
删除
BIDQuartzFunView.h和BIDQuartzFunView.m文件。
创建BIDGLFunView
-
选择GLFun文件夹并按
⌘N创建新文件,选择Objective-C class并点击Next。 -
将类命名为
BIDGLFunView并使其成为OpenGLES2DView的子类。 -
点击
Next并将文件保存到项目文件夹中。 -
替换
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 -
在
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
```
-
(id)initWithCoder:(NSCoder*)coder {
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并绘制。最后,渲染缓冲区并显示。以下是详细的步骤和代码解释:
-
重置虚拟世界 :
objc glLoadIdentity();
这行代码用于重置虚拟世界,清除之前可能应用的旋转、平移或其他变换。 -
清除背景 :
objc glClearColor(0.78f, 0.78f, 0.78f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
这里将背景颜色设置为特定的灰色,并清除颜色缓冲区。 -
设置绘图颜色 :
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的绘图颜色。 -
关闭纹理映射 :
objc glDisable(GL_TEXTURE_2D);
关闭纹理映射功能,确保绘制的颜色能够显示出来。 -
定义顶点数组 :
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坐标的转换。 -
指定线宽并绘制 :
objc glLineWidth(2.0); glVertexPointer(2, GL_FLOAT, 0, vertices); glDrawArrays(GL_LINES, 0, 2);
设置线宽为2.0,传递顶点数组给OpenGL ES,并绘制直线。 -
渲染并显示缓冲区 :
objc glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES];
绑定渲染缓冲区并显示。
绘制椭圆
OpenGL ES没有直接绘制椭圆的函数,需要手动定义顶点数组来模拟椭圆。具体步骤如下:
-
重置和清除 :
objc glLoadIdentity(); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_TEXTURE_2D);
重置虚拟世界,清除背景为白色,关闭纹理映射。 -
设置绘图颜色 :
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分量并设置绘图颜色。 -
定义顶点数组 :
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; }
计算椭圆的水平和垂直半径,通过循环计算椭圆上每个点的坐标,并存储在顶点数组中。 -
绘制和渲染 :
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绘图的操作步骤和流程。在实际应用中,根据具体需求选择合适的绘图方式,并进行相应的优化,以达到最佳的绘图效果和性能。
Quartz与OpenGL ES绘图技术对比
超级会员免费看
16

被折叠的 条评论
为什么被折叠?



