3. 简单图形填充器
功能:实现纯色填充和渐变色填充功能,为绘制的封闭图形填充颜色。
要求:支持 RGB 颜色模式,可自定义渐变色起始与终止颜色,展示填充过程的动画效果。
目录
一、功能需求与技术路线
简单图形填充器分析
1. 功能模块划分
- 填充模式管理:区分纯色填充与渐变色填充,支持动态切换模式。
- 颜色输入模块:支持RGB数值输入或颜色选择器交互,分别设置纯色、渐变起始/终止颜色。
- 动画控制器:管理填充进度(如从0%到100%逐步填充),控制动画时长与插值方式。
- 图形数据扩展:存储图形边界信息、填充状态(是否填充、动画进度)及渐变参数。
2. 核心实现逻辑
- 纯色填充算法:
- 扫描线填充:针对凸多边形,按行扫描交点并填充区间。
- 种子填充(边界/泛洪):适用于任意封闭图形,需处理栈溢出风险。
- OpenGL内置填充:通过
glPolygonMode(GL_FILL)
直接绘制,但需确保图形闭合且无自交。
- 渐变色填充算法:
- 线性渐变:基于图形坐标系插值颜色(如从左到右),通过片段着色器计算片元颜色。
- 径向渐变:以图形中心为原点,按半径插值颜色,需计算片元到中心的距离。
- 参数化渐变方向:允许用户选择渐变轴(水平、垂直、对角线等)。
3. 数据结构设计
- 图形对象扩展:
- 存储图形类型、位置、尺寸等基础属性。
- 新增填充属性:是否填充、纯色值(RGB)、渐变参数(起始/终止颜色、方向)、动画进度(0.0~1.0)。
- 渐变方向枚举:如
HORIZONTAL
、VERTICAL
、DIAGONAL
。
4. OpenGL实现策略
- 纯色填充:
- 使用
glBegin(GL_POLYGON)
绘制闭合路径,设置glColor3ub
为纯色。 - 启用深度测试避免重叠图形填充错误。
- 使用
- 渐变色填充:
- 顶点着色器:传递渐变起止坐标或方向参数。
- 片段着色器:根据片元位置计算插值颜色(如线性渐变中根据片元坐标与渐变方向的投影比例插值颜色值)。
- 动画实现:通过
glUniform1f
传递动画进度参数,片段着色器根据进度动态裁剪填充区域(如仅填充进度≤当前进度的部分)。
5. 交互与性能优化
- 颜色输入交互:
- 提供RGB滑块或集成颜色选择器,实时更新预览效果。
- 动画性能:
- 使用离屏渲染预计算渐变纹理,避免每帧重复计算。
- 对静态图形缓存填充结果,减少GPU负载。
- 抗锯齿处理:启用混合(
glBlendFunc
)平滑填充边缘。
6. 技术难点与解决方案
- 复杂图形填充:
- 对非凸多边形使用扫描线算法或分割为三角形逐块填充。
- 渐变方向对齐:
- 将渐变方向映射到世界坐标系,确保图形旋转时渐变方向一致。
- 动画卡顿:
- 采用固定时间步长更新动画,避免帧率波动导致进度跳跃。
7. 扩展性设计
- 多渐变类型支持:预留接口支持径向、圆锥渐变。
- 填充模式混合:允许叠加纹理与颜色渐变。
- 物理模拟填充:如液体扩散效果,需引入噪声函数与时间变量。
二、核心算法实现
2.1 颜色插值原理
2.1.1 线性插值公式
float ratio = currentPos / totalLength;
float r = rStart * (1 - ratio) + rEnd * ratio;
应用场景:
- 顶点渐变:按顶点位置权重插值
- 扫描线动画:按扫描线位置插值
2.1.2 扫描线填充算法
for (int y = startY; y < endY; y++) {
float lineRatio = (y - startY) / (float)(endY - startY);
// 根据lineRatio计算当前行颜色
}
优化技巧:
- 使用整数运算代替浮点运算提升性能
- 预计算颜色梯度表减少重复计算
三、代码架构解析
3.1 系统模块划分
class FillSystem {
public:
void initPolygon(); // 初始化测试多边形
void drawSolid(); // 纯色填充模式
void drawGradient(); // Gouraud着色模式
void drawScanline(); // 扫描线动画模式
private:
std::vector<std::pair<float,float>> vertices; // 多边形顶点
float colorStart[3]; // 起始颜色RGB
float colorEnd[3]; // 终止颜色RGB
float animationProgress; // 动画进度(0.0~1.0)
};
3.2 关键代码实现
3.2.1 顶点渐变填充
void drawGradient() {
glBegin(GL_POLYGON);
for (size_t i = 0; i < vertices.size(); ++i) {
// 计算顶点间颜色插值
float ratio = i / (float)(vertices.size()-1);
glColor3f(
colorStart[0]*(1-ratio) + colorEnd[0]*ratio,
colorStart[1]*(1-ratio) + colorEnd[1]*ratio,
colorStart[2]*(1-ratio) + colorEnd[2]*ratio
);
glVertex2f(vertices[i].first, vertices[i].second);
}
glEnd();
}
技术亮点:
- 使用GL_SMOOTH着色模式启用顶点插值
- 线性插值保证颜色过渡平滑
3.2.2 扫描线动画实现
void drawScanline() {
int currentLine = animationProgress * H;
for (int y = -H/2; y < -H/2 + currentLine; ++y) {
// 计算垂直方向颜色渐变
float ratio = (y + H/2) / (float)H;
glColor3f(
colorStart[0]*(1-ratio) + colorEnd[0]*ratio,
colorStart[1]*(1-ratio) + colorEnd[1]*ratio,
colorStart[2]*(1-ratio) + colorEnd[2]*ratio
);
// 绘制水平扫描线
glBegin(GL_LINES);
glVertex2f(-100, y);
glVertex2f(100, y);
glEnd();
}
}
性能优化:
- 限制最大绘制行数为窗口高度
- 使用批量绘制减少状态切换
四、关键技术详解
4.1 双缓冲动画机制
void timerFunc(int value) {
animationProgress += 0.005f;
if (animationProgress > 1.0f) animationProgress = 0.0f;
glutPostRedisplay(); // 触发双缓冲交换
glutTimerFunc(16, timerFunc, 0);
}
工作原理:
- 前缓冲:显示当前帧
- 后缓冲:绘制下一帧
- 帧完成后交换缓冲
4.2 颜色空间管理
// RGB颜色存储结构(C++98兼容写法)
struct Color {
float r, g, b;
Color(float r=0, float g=0, float b=0) : r(r), g(g), b(b) {}
Color interpolate(const Color& end, float t) const {
return Color(
r*(1-t) + end.r*t,
g*(1-t) + end.g*t,
b*(1-t) + end.b*t
);
}
};
扩展性:
- 支持HSV颜色空间转换
- 可添加透明度通道
五、功能扩展方向
5.1 填充模式增强
扩展功能 | 实现方案 | 技术难点 |
---|---|---|
纹理映射填充 | 加载位图纹理并映射到多边形 | 纹理坐标计算与UV展开 |
模式混合填充 | 多种填充模式叠加 | Alpha通道混合计算 |
动态渐变路径 | 沿任意路径进行颜色渐变 | 路径参数化与积分计算 |
5.2 交互功能升级
// 添加颜色选择对话框(Windows API)
COLORREF selectedColor = ChooseColor(colorDialog);
glColor3f(
GetRValue(selectedColor)/255.0f,
GetGValue(selectedColor)/255.0f,
GetBValue(selectedColor)/255.0f
);
实现要点:
- 将RGB值从[0,255]转换到[0,1]区间
- 支持颜色拾取器交互
六、完整代码
// ===================================================================
// 文件名:SimpleFiller_Cpp98.cpp
// 功能:简单图形填充器,支持纯色、顶点渐变和扫描线渐变动画
// 编译(Dev-C++ 4.9.2 32-bit)链接:
// -lopengl32 -lglu32 -lglut32 -lwinmm -lgdi32
// ===================================================================
#define GLUT_DISABLE_ATEXIT_HACK
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <vector>
#include <utility> // std::pair, std::make_pair
#include <cmath>
// 窗口尺寸
const int W = 800;
const int H = 600;
// 渐变进度 [0,1]
float t = 0.0f;
const float dt = 0.005f; // 每帧增量
// 颜色起始与终止(可根据需要修改)
float r1 = 1.0f, g1 = 0.0f, b1 = 0.0f; // 红色起始
float r2 = 0.0f, g2 = 0.0f, b2 = 1.0f; // 蓝色结束
// 多边形顶点(正方形),使用 std::vector 和 std::pair<C++98 写法>
std::vector< std::pair<float, float> > poly;
// 模式切换
bool showSolid = false;
bool showGouraud = true;
bool showScan = false;
// -------------------------------------------------------------------
// 初始化多边形顶点
void initPoly() {
poly.clear();
poly.push_back(std::make_pair(-100.0f, -100.0f));
poly.push_back(std::make_pair( 100.0f, -100.0f));
poly.push_back(std::make_pair( 100.0f, 100.0f));
poly.push_back(std::make_pair(-100.0f, 100.0f));
}
// -------------------------------------------------------------------
// OpenGL 初始化
void initGL(){
glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 白背景
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-W/2, W/2, -H/2, H/2);
}
// -------------------------------------------------------------------
// 纯色填充
void drawSolid(){
glColor3f(r1, g1, b1);
glBegin(GL_POLYGON);
for (size_t i = 0; i < poly.size(); ++i) {
glVertex2f(poly[i].first, poly[i].second);
}
glEnd();
}
// -------------------------------------------------------------------
// 顶点渐变填充 (Gouraud)
// 利用顶点颜色插值
void drawGradient(){
glShadeModel(GL_SMOOTH);
glBegin(GL_POLYGON);
size_t n = poly.size();
for (size_t i = 0; i < n; ++i) {
float ratio = float(i) / float(n - 1);
float rc = r1 * (1 - ratio) + r2 * ratio;
float gc = g1 * (1 - ratio) + g2 * ratio;
float bc = b1 * (1 - ratio) + b2 * ratio;
glColor3f(rc, gc, bc);
glVertex2f(poly[i].first, poly[i].second);
}
glEnd();
}
// -------------------------------------------------------------------
// 扫描线渐变动画
void drawScanline(){
int totalLines = H;
int currentLine = int(t * totalLines);
for (int y = -H/2; y < -H/2 + currentLine; ++y) {
float ratio = float(y + H/2) / float(totalLines);
float rc = r1 * (1 - ratio) + r2 * ratio;
float gc = g1 * (1 - ratio) + g2 * ratio;
float bc = b1 * (1 - ratio) + b2 * ratio;
glColor3f(rc, gc, bc);
glBegin(GL_LINES);
glVertex2f(-100.0f, (float)y);
glVertex2f( 100.0f, (float)y);
glEnd();
}
}
// -------------------------------------------------------------------
// 显示回调
void display(){
glClear(GL_COLOR_BUFFER_BIT);
if (showSolid) {
drawSolid();
}
if (showGouraud) {
drawGradient();
}
if (showScan) {
drawScanline();
}
glutSwapBuffers();
}
// -------------------------------------------------------------------
// 定时器:更新 t 并重绘
void timerFunc(int value){
t += dt;
if (t > 1.0f) t = 0.0f;
glutPostRedisplay();
glutTimerFunc(16, timerFunc, 0); // ~60 FPS
}
// -------------------------------------------------------------------
// 键盘回调:切换模式
void keyboard(unsigned char key, int x, int y){
switch (key) {
case '1':
showSolid = true;
showGouraud = showScan = false;
break;
case '2':
showGouraud = true;
showSolid = showScan = false;
break;
case '3':
showScan = true;
showSolid = showGouraud = false;
break;
case 27: // ESC
exit(0);
break;
default:
break;
}
}
// -------------------------------------------------------------------
// 主函数
int main(int argc, char** argv){
initPoly();
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(W, H);
glutInitWindowPosition(100, 100);
glutCreateWindow("Simple Shape Filler (C++98)");
initGL();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
glutTimerFunc(0, timerFunc, 0);
glutMainLoop();
return 0;
}