OpenGL 蓝书学习(第四版)第七章
OpenGL 蓝书学习(第四版)第七章
固定管线像素图显示
画位图
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <numbers>
#include <ostream>
#include <iostream>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
/*
* FIRE 位图数据,用于 glBitmap 渲染
* 32 * 32 位图,黑色背景,白色火焰形状
* 每个字节表示 8 个像素,1 表示白色,0 表示黑色
* 共 128 字节 (32 * 32 / 8)
* 从左到右,从上到下排列
*/
static constexpr GLubyte FIRE[128] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x07, 0xf0,
0x0f, 0x00, 0x1f, 0xe0, 0x1f, 0x80, 0x1f, 0xc0, 0x0f, 0xc0, 0x3f, 0x80,
0x07, 0xe0, 0x7e, 0x00, 0x03, 0xf0, 0xff, 0x80, 0x03, 0xf5, 0xff, 0xe0,
0x07, 0xfd, 0xff, 0xf8, 0x1f, 0xfc, 0xff, 0xe8, 0xff, 0xe3, 0xbf, 0x70,
0xde, 0x80, 0xb7, 0x00, 0x71, 0x10, 0x4a, 0x80, 0x03, 0x10, 0x4e, 0x40,
0x02, 0x88, 0x8c, 0x20, 0x05, 0x05, 0x04, 0x40, 0x02, 0x82, 0x14, 0x40,
0x02, 0x40, 0x10, 0x80, 0x02, 0x64, 0x1a, 0x80, 0x00, 0x92, 0x29, 0x00,
0x00, 0xb0, 0x48, 0x00, 0x00, 0xc8, 0x90, 0x00, 0x00, 0x85, 0x10, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00
};
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
void renderScene(){
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0F, 1.0F, 1.0F);
for(int i = 0; i < 2; i ++ ){
glRasterPos2i(0, i * 32);
for(int j = 0; j < 3; j ++ ){
glBitmap(32, 32, 0.0, 0.0, 32.0, 0.0, FIRE);
}
}
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.25F, 0.25F, 0.25F, 1.0F);
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
const int mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
setupRc();
glutMainLoop();
return 0;
}
加载tga图片
#include <ostream>
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
void renderScene(){
glClear(GL_COLOR_BUFFER_BIT);
std::ifstream file("../fire.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open fire.tga" << std::endl;
return;
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
return;
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
const auto imageData = std::make_unique<GLbyte[]>(imageSize);
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
return;
}
std::cout << "Successfully read Targa image data" << std::endl;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDrawPixels(header.width, header.height, GL_BGR, GL_UNSIGNED_BYTE, imageData.get());
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.25F, 0.25F, 0.25F, 1.0F);
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
const int mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
setupRc();
glutMainLoop();
return 0;
}
加载png图片
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
void renderScene(){
glClear(GL_COLOR_BUFFER_BIT);
stbi_set_flip_vertically_on_load(1); // PNG/JPG 行序是"自上而下",翻转为 OpenGL 习惯的"自下而上"
int width, height, channels;
using ImageData = std::unique_ptr<unsigned char[], decltype(&stbi_image_free)>;
ImageData imageData(stbi_load("../image.png", &width, &height, &channels, 4), &stbi_image_free);
if (!imageData) {
std::cerr << "Failed to load image.png" << std::endl;
return;
}
std::cout << "Successfully read image.png data" << ", width=" << width << ", height=" << height << ", channels=" << channels << std::endl;
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_BYTE, imageData.get());
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.25F, 0.25F, 0.25F, 1.0F);
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
const int mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
std::cout << "Current path: " << std::filesystem::current_path() << std::endl;
setupRc();
glutMainLoop();
return 0;
}
8种特效
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 1;
static GLint mainWindowId = 0; // 主窗口 ID
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
void renderScene(){
glClear(GL_COLOR_BUFFER_BIT);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
// 中心绘制一个圆
constexpr int numSegments = 100;
constexpr float radius = 50.0f;
glColor3f(1.0f, 0.0f, 0.0f); // 红色
glBegin(GL_LINE_LOOP);
for (int i = 0; i < numSegments; ++i) {
float angle = 2.0f * PI * i / numSegments;
float x = 800.0F / 2 + radius * cos(angle);
float y = 600.0F / 2 + radius * sin(angle);
glVertex2f(x, y);
}
glEnd();
switch(renderMode){
case 0:
glPixelZoom(1.0F, 1.0F);
glRasterPos2i(0, 0);
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
break;
case 2:
glPixelZoom(-1.0F, -1.0F);
glRasterPos2i(targaData.first.width, targaData.first.height);
break;
case 3:
// 放大
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(static_cast<GLfloat>(viewport[2]) / targaData.first.width,
static_cast<GLfloat>(viewport[3]) / targaData.first.height);
glRasterPos2i(0, 0);
break;
case 4:
// 红色通道显示
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 0.0F);
glPixelTransferf(GL_BLUE_SCALE, 0.0F);
break;
case 5:
// 绿色通道显示
glPixelTransferf(GL_RED_SCALE, 0.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 0.0F);
break;
case 6:
// 蓝色通道显示
glPixelTransferf(GL_RED_SCALE, 0.0F);
glPixelTransferf(GL_GREEN_SCALE, 0.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
break;
case 7: {
// 下面三行解决过度饱和
glPixelTransferf(GL_RED_SCALE, 0.3F);
glPixelTransferf(GL_GREEN_SCALE, 0.59F);
glPixelTransferf(GL_BLUE_SCALE, 0.11F);
const auto newImageData = std::make_unique<GLbyte[]>(targaData.first.width * targaData.first.height);
glReadPixels(0, 0, targaData.first.width, targaData.first.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, newImageData.get());
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
glDrawPixels(targaData.first.width, targaData.first.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, newImageData.get());
break;
}
case 8: {
GLfloat invertMap[256] = {0.0F};
for (int i = 0; i < 256; ++i) {
invertMap[i] = 1.0F - (1.0F / 255.0F) * static_cast<GLfloat>(i);
}
glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 256, invertMap);
glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 256, invertMap);
glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 256, invertMap);
glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
break;
}
default:
break;
}
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
void writeTarga(const std::string& filename, const TargaHeader& header, const GLbyte* imageData) {
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize = newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // ← 注意是 PACK,读像素时用
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, newHeader.width, newHeader.height, GL_BGR, GL_UNSIGNED_BYTE, imageBuffer.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
switch(option){
case 1:
std::cout << "Option 1 selected" << std::endl;
writeTarga("output.tga", targaData.first, targaData.second.get());
break;
default:
renderMode = option;
break;
}
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("保存", 1);
glutAddMenuEntry("旋转180度", 2);
glutAddMenuEntry("放大", 3);
glutAddMenuEntry("红色", 4);
glutAddMenuEntry("绿色", 5);
glutAddMenuEntry("蓝色", 6);
glutAddMenuEntry("黑白", 7);
glutAddMenuEntry("颜色反转", 8);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
glutMainLoop();
return 0;
}
保存为png
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 1;
static GLint mainWindowId = 0; // 主窗口 ID
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
void renderScene(){
glClear(GL_COLOR_BUFFER_BIT);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
// 中心绘制一个圆
constexpr int numSegments = 100;
constexpr float radius = 50.0f;
glColor3f(1.0f, 0.0f, 0.0f); // 红色
glBegin(GL_LINE_LOOP);
for (int i = 0; i < numSegments; ++i) {
float angle = 2.0f * PI * i / numSegments;
float x = 800.0F / 2 + radius * cos(angle);
float y = 600.0F / 2 + radius * sin(angle);
glVertex2f(x, y);
}
glEnd();
switch(renderMode){
case 0:
glPixelZoom(1.0F, 1.0F);
glRasterPos2i(0, 0);
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
break;
case 2:
glPixelZoom(-1.0F, -1.0F);
glRasterPos2i(targaData.first.width, targaData.first.height);
break;
case 3:
// 放大
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(static_cast<GLfloat>(viewport[2]) / targaData.first.width,
static_cast<GLfloat>(viewport[3]) / targaData.first.height);
glRasterPos2i(0, 0);
break;
case 4:
// 红色通道显示
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 0.0F);
glPixelTransferf(GL_BLUE_SCALE, 0.0F);
break;
case 5:
// 绿色通道显示
glPixelTransferf(GL_RED_SCALE, 0.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 0.0F);
break;
case 6:
// 蓝色通道显示
glPixelTransferf(GL_RED_SCALE, 0.0F);
glPixelTransferf(GL_GREEN_SCALE, 0.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
break;
case 7: {
// 下面三行解决过度饱和
glPixelTransferf(GL_RED_SCALE, 0.3F);
glPixelTransferf(GL_GREEN_SCALE, 0.59F);
glPixelTransferf(GL_BLUE_SCALE, 0.11F);
const auto newImageData = std::make_unique<GLbyte[]>(targaData.first.width * targaData.first.height);
glReadPixels(0, 0, targaData.first.width, targaData.first.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, newImageData.get());
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
glDrawPixels(targaData.first.width, targaData.first.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, newImageData.get());
break;
}
case 8: {
GLfloat invertMap[256] = {0.0F};
for (int i = 0; i < 256; ++i) {
invertMap[i] = 1.0F - (1.0F / 255.0F) * static_cast<GLfloat>(i);
}
glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 256, invertMap);
glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 256, invertMap);
glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 256, invertMap);
glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
break;
}
default:
break;
}
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
void writePng(const std::string& filename, const int width, const int height) {
glPixelTransferi(GL_MAP_COLOR, GL_FALSE); // 禁用颜色映射
// openGL 的 glReadPixels 读取的图像是自下而上的,而 PNG 是自上而下的,所以需要翻转图像
stbi_flip_vertically_on_write(1);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // 设置像素存储模式,确保行对齐为1
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
const std::size_t imageSize = width * height * 3; // 3 bytes per pixel for RGB
const auto flippedImageData = std::make_unique<GLbyte[]>(imageSize);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, flippedImageData.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
if (!stbi_write_png(filename.c_str(), width, height, 3, flippedImageData.get(), width * 3)) {
std::cerr << "Failed to write PNG file: " << filename << std::endl;
throw std::runtime_error("Failed to write PNG file");
}
}
void writeTarga(const std::string& filename, const TargaHeader& header) {
glPixelTransferi(GL_MAP_COLOR, GL_FALSE); // 禁用颜色映射
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize =
// renderMode == 8 ? newHeader.width * newHeader.height
newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // ← 注意是 PACK,读像素时用
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, newHeader.width, newHeader.height, GL_BGR, GL_UNSIGNED_BYTE, imageBuffer.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
switch(option){
case 1:
std::cout << "Option 1 selected" << std::endl;
// writeTarga("output.tga", targaData.first, targaData.second.get());
writePng("output.png", targaData.first.width, targaData.first.height);
break;
default:
renderMode = option;
break;
}
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("保存", 1);
glutAddMenuEntry("旋转180度", 2);
glutAddMenuEntry("放大", 3);
glutAddMenuEntry("红色", 4);
glutAddMenuEntry("绿色", 5);
glutAddMenuEntry("蓝色", 6);
glutAddMenuEntry("黑白", 7);
glutAddMenuEntry("颜色反转", 8);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
glutMainLoop();
return 0;
}
扩展查询
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 1;
static GLint mainWindowId = 0; // 主窗口 ID
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
void renderScene(){
glClear(GL_COLOR_BUFFER_BIT);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
// 中心绘制一个圆
constexpr int numSegments = 100;
constexpr float radius = 50.0f;
glColor3f(1.0f, 0.0f, 0.0f); // 红色
glBegin(GL_LINE_LOOP);
for (int i = 0; i < numSegments; ++i) {
float angle = 2.0f * PI * i / numSegments;
float x = 800.0F / 2 + radius * cos(angle);
float y = 600.0F / 2 + radius * sin(angle);
glVertex2f(x, y);
}
glEnd();
switch(renderMode){
case 0:
glPixelZoom(1.0F, 1.0F);
glRasterPos2i(0, 0);
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
break;
case 2:
glPixelZoom(-1.0F, -1.0F);
glRasterPos2i(targaData.first.width, targaData.first.height);
break;
case 3:
// 放大
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(static_cast<GLfloat>(viewport[2]) / targaData.first.width,
static_cast<GLfloat>(viewport[3]) / targaData.first.height);
glRasterPos2i(0, 0);
break;
case 4:
// 红色通道显示
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 0.0F);
glPixelTransferf(GL_BLUE_SCALE, 0.0F);
break;
case 5:
// 绿色通道显示
glPixelTransferf(GL_RED_SCALE, 0.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 0.0F);
break;
case 6:
// 蓝色通道显示
glPixelTransferf(GL_RED_SCALE, 0.0F);
glPixelTransferf(GL_GREEN_SCALE, 0.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
break;
case 7: {
// 下面三行解决过度饱和
glPixelTransferf(GL_RED_SCALE, 0.3F);
glPixelTransferf(GL_GREEN_SCALE, 0.59F);
glPixelTransferf(GL_BLUE_SCALE, 0.11F);
const auto newImageData = std::make_unique<GLbyte[]>(targaData.first.width * targaData.first.height);
glReadPixels(0, 0, targaData.first.width, targaData.first.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, newImageData.get());
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
glDrawPixels(targaData.first.width, targaData.first.height, GL_LUMINANCE, GL_UNSIGNED_BYTE, newImageData.get());
break;
}
case 8: {
GLfloat invertMap[256] = {0.0F};
for (int i = 0; i < 256; ++i) {
invertMap[i] = 1.0F - (1.0F / 255.0F) * static_cast<GLfloat>(i);
}
glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 256, invertMap);
glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 256, invertMap);
glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 256, invertMap);
glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
break;
}
default:
break;
}
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
void writePng(const std::string& filename, const int width, const int height) {
// openGL 的 glReadPixels 读取的图像是自下而上的,而 PNG 是自上而下的,所以需要翻转图像
stbi_flip_vertically_on_write(1);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // 设置像素存储模式,确保行对齐为1
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
const std::size_t imageSize = width * height * 3; // 3 bytes per pixel for RGB
const auto flippedImageData = std::make_unique<GLbyte[]>(imageSize);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, flippedImageData.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
if (!stbi_write_png(filename.c_str(), width, height, 3, flippedImageData.get(), width * 3)) {
std::cerr << "Failed to write PNG file: " << filename << std::endl;
throw std::runtime_error("Failed to write PNG file");
}
}
void writeTarga(const std::string& filename, const TargaHeader& header) {
glPixelTransferi(GL_MAP_COLOR, GL_FALSE); // 禁用颜色映射
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize =
// renderMode == 8 ? newHeader.width * newHeader.height
newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // ← 注意是 PACK,读像素时用
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, newHeader.width, newHeader.height, GL_BGR, GL_UNSIGNED_BYTE, imageBuffer.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
switch(option){
case 1:
std::cout << "Option 1 selected" << std::endl;
writeTarga("output.tga", targaData.first);
break;
default:
renderMode = option;
break;
}
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("保存", 1);
glutAddMenuEntry("旋转180度", 2);
glutAddMenuEntry("放大", 3);
glutAddMenuEntry("红色", 4);
glutAddMenuEntry("绿色", 5);
glutAddMenuEntry("蓝色", 6);
glutAddMenuEntry("黑白", 7);
glutAddMenuEntry("颜色反转", 8);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
std::cout << "GL_ARB_imaging supported: " << glutExtensionSupported("GL_ARB_imaging") << std::endl;
glutMainLoop();
return 0;
}
图像处理
#include <OpenGL/glext.h>
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 0;
static GLint mainWindowId = 0; // 主窗口 ID
static GLboolean isHistogram = GL_FALSE; // 是否显示直方图
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
void renderScene(){
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
// 光栅(Raster)位置设置
glRasterPos2i(0, 0);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(viewport[2] / static_cast<GLfloat>(targaData.first.width), viewport[3] / static_cast<GLfloat>(targaData.first.height)); // 设置像素缩放因子为默认值
static constexpr GLfloat lumMat[16] = {
0.30f, 0.30f, 0.30f, 0.0f,
0.59f, 0.59f, 0.59f, 0.0f,
0.11f, 0.11f, 0.11f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
if (isHistogram) {
glMatrixMode(GL_COLOR);
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE);
glEnable(GL_HISTOGRAM);
}
switch (renderMode) {
case 5: {
static constexpr GLfloat sharpen[3 * 3] = {
0.0f, -1.0f, 0.0f,
-1.0f, 5.0f, -1.0f,
0.0f, -1.0f, 0.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, sharpen);
glEnable(GL_CONVOLUTION_2D);
break;
}
case 4: {
static constexpr GLfloat emboss[3 * 3] = {
2.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, 0.0f, -1.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, emboss);
glEnable(GL_CONVOLUTION_2D);
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 3: { // 反转图像
static GLubyte invertTable[256][3]{};
for (int i = 0; i < 256; ++i) {
invertTable[i][0] = 255 - i;
invertTable[i][1] = 255 - i;
invertTable[i][2] = 255 - i;
}
glColorTable(GL_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, invertTable);
glColorTable(GL_PROXY_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
GLint width;
glGetColorTableParameteriv(GL_PROXY_COLOR_TABLE, GL_COLOR_TABLE_WIDTH, &width);
std::cout << "Proxy color table width: " << width << std::endl;
glEnable(GL_COLOR_TABLE);
break;
}
case 2: { // 亮化图片
glMatrixMode(GL_COLOR);
glScalef(1.25F, 1.25F, 1.25F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 1: { // 暗化图片
glMatrixMode(GL_COLOR);
glScalef(0.8F, 0.8F, 0.8F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
default:
// 默认渲染模式的处理逻辑
break;
}
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
if (isHistogram) {
std::cout << "Displaying histogram..." << std::endl;
static GLint histogram[256];
glGetHistogram(GL_HISTOGRAM, GL_TRUE, GL_LUMINANCE, GL_INT, histogram);
GLint largest = 0;
for(GLint i = 0; i < 256; ++i) {
if (histogram[i] > largest) {
largest = histogram[i];
}
}
glColor3f(1.0F, 1.0F, 1.0F);
glBegin(GL_LINE_STRIP);
for (GLint i = 0; i < 256; ++i) {
// glVertex2f(static_cast<GLfloat>(i) / 255.0F, static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest));
glVertex2f(static_cast<GLfloat>(i), static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest) * 128.0F);
}
glEnd();
// 不要在这里清 isHistogram:GLUT 常在菜单关闭后再重绘一次,
// 若此刻清掉标志,下一帧 glClear + glDrawPixels 就会把直方图覆盖掉。
glDisable(GL_HISTOGRAM);
}
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadIdentity(); // 重置颜色矩阵
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glDisable(GL_CONVOLUTION_2D);
glDisable(GL_COLOR_TABLE); // 禁用颜色表
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
void writePng(const std::string& filename, const int width, const int height) {
// openGL 的 glReadPixels 读取的图像是自下而上的,而 PNG 是自上而下的,所以需要翻转图像
stbi_flip_vertically_on_write(1);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // 设置像素存储模式,确保行对齐为1
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
const std::size_t imageSize = width * height * 3; // 3 bytes per pixel for RGB
const auto flippedImageData = std::make_unique<GLbyte[]>(imageSize);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, flippedImageData.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
if (!stbi_write_png(filename.c_str(), width, height, 3, flippedImageData.get(), width * 3)) {
std::cerr << "Failed to write PNG file: " << filename << std::endl;
throw std::runtime_error("Failed to write PNG file");
}
}
void writeTarga(const std::string& filename, const TargaHeader& header) {
glPixelTransferi(GL_MAP_COLOR, GL_FALSE); // 禁用颜色映射
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize =
// renderMode == 8 ? newHeader.width * newHeader.height
newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
glPixelStorei(GL_PACK_ALIGNMENT, 1); // ← 注意是 PACK,读像素时用
// 获取当前读的是前缓冲区还是后缓冲区
GLint currentBuffer;
glGetIntegerv(GL_READ_BUFFER, ¤tBuffer); // 获取当前像素存储模式
// 16进制输出当前读缓冲区
std::cout << std::format("Current read buffer: 0x{:X}", currentBuffer) << std::endl;
glReadBuffer(GL_FRONT);
glReadPixels(0, 0, newHeader.width, newHeader.height, GL_BGR, GL_UNSIGNED_BYTE, imageBuffer.get());
glReadBuffer(currentBuffer); // 恢复之前的读缓冲区
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
renderMode = option;
isHistogram = (option == 6) ? GL_TRUE : GL_FALSE; // 只有直方图模式开启,切到其它模式自动关闭
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("暗化图片", 1);
glutAddMenuEntry("亮化图片", 2);
glutAddMenuEntry("反转图片", 3);
glutAddMenuEntry("浮雕化图片", 4);
glutAddMenuEntry("锐化图片", 5);
glutAddMenuEntry("直方图均衡化", 6);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
std::cout << "GL_ARB_imaging supported: " << glutExtensionSupported("GL_ARB_imaging") << std::endl;
glutMainLoop();
return 0;
}
图片blur
#ifdef __APPLE__
#include <OpenGL/glext.h>
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <array> // std::array(高斯核)
#include <format> // std::format
#include <stdexcept> // std::runtime_error
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 0;
static GLint mainWindowId = 0; // 主窗口 ID
static GLboolean isHistogram = GL_FALSE; // 是否显示直方图
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
void renderScene(){
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
// 光栅(Raster)位置设置
glRasterPos2i(0, 0);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(viewport[2] / static_cast<GLfloat>(targaData.first.width), viewport[3] / static_cast<GLfloat>(targaData.first.height)); // 设置像素缩放因子为默认值
static constexpr GLfloat lumMat[16] = {
0.30f, 0.30f, 0.30f, 0.0f,
0.59f, 0.59f, 0.59f, 0.0f,
0.11f, 0.11f, 0.11f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
if (isHistogram) {
glMatrixMode(GL_COLOR);
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE);
glEnable(GL_HISTOGRAM);
}
switch (renderMode) {
case 5: {
static constexpr GLfloat sharpen[3 * 3] = {
0.0f, -1.0f, 0.0f,
-1.0f, 5.0f, -1.0f,
0.0f, -1.0f, 0.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, sharpen);
glEnable(GL_CONVOLUTION_2D);
break;
}
case 4: {
static constexpr GLfloat emboss[3 * 3] = {
2.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, 0.0f, -1.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, emboss);
glEnable(GL_CONVOLUTION_2D);
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 3: { // 反转图像
// 反转表的生成与代理查询只做一次:它们是静态开销,每帧重复既浪费又会刷屏
static GLubyte invertTable[256][3]{};
static bool invertTableReady = false;
if (!invertTableReady) {
for (int i = 0; i < 256; ++i) {
invertTable[i][0] = static_cast<GLubyte>(255 - i);
invertTable[i][1] = static_cast<GLubyte>(255 - i);
invertTable[i][2] = static_cast<GLubyte>(255 - i);
}
glColorTable(GL_PROXY_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
GLint width = 0;
glGetColorTableParameteriv(GL_PROXY_COLOR_TABLE, GL_COLOR_TABLE_WIDTH, &width);
std::cout << "Proxy color table width: " << width << " (仅首次查询)" << std::endl;
invertTableReady = true;
}
glColorTable(GL_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, invertTable);
glEnable(GL_COLOR_TABLE);
break;
}
case 2: { // 亮化图片
glMatrixMode(GL_COLOR);
glScalef(1.25F, 1.25F, 1.25F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 1: { // 暗化图片
glMatrixMode(GL_COLOR);
glScalef(0.8F, 0.8F, 0.8F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 7: { // 模糊:高斯模糊(9×9,σ=2.5)—— 调 kSigma / kRadius 即可改模糊强度
constexpr int kRadius = 4; // 半径:2 → 5×5;4 → 9×9(越大越糊)
constexpr float kSigma = 5.5F; // 标准差:越大越糊
constexpr int kSize = kRadius * 2 + 1;
// 按二维高斯公式生成权重并归一化(只算一次),不必再手写几十个数字
static const std::array<GLfloat, kSize * kSize> gaussian = [] {
std::array<GLfloat, kSize * kSize> weights{};
GLfloat sum = 0.0F;
for (int y = -kRadius; y <= kRadius; ++y) {
for (int x = -kRadius; x <= kRadius; ++x) {
const GLfloat w = std::exp(-static_cast<GLfloat>(x * x + y * y) /
(2.0F * kSigma * kSigma));
weights[(y + kRadius) * kSize + (x + kRadius)] = w;
sum += w;
}
}
for (GLfloat& w : weights) {
w /= sum; // 归一化:总权重为 1,整体亮度不变
}
return weights;
}();
// 合法边界模式只有 GL_REDUCE(默认,输出会缩小)、GL_CONSTANT_BORDER、GL_REPLICATE_BORDER;
// 误用 GL_REPEAT 在 macOS 上不报错,但第二次绘制会段错误
glConvolutionParameteri(GL_CONVOLUTION_2D, GL_CONVOLUTION_BORDER_MODE, GL_REPLICATE_BORDER);
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, kSize, kSize, GL_LUMINANCE, GL_FLOAT, gaussian.data());
glEnable(GL_CONVOLUTION_2D);
break;
}
default:
// 默认渲染模式的处理逻辑
break;
}
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
if (isHistogram) {
std::cout << "Displaying histogram..." << std::endl;
static GLint histogram[256];
glGetHistogram(GL_HISTOGRAM, GL_TRUE, GL_LUMINANCE, GL_INT, histogram);
GLint largest = 0;
for(GLint i = 0; i < 256; ++i) {
if (histogram[i] > largest) {
largest = histogram[i];
}
}
glColor3f(1.0F, 1.0F, 1.0F);
glBegin(GL_LINE_STRIP);
for (GLint i = 0; i < 256; ++i) {
// glVertex2f(static_cast<GLfloat>(i) / 255.0F, static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest));
glVertex2f(static_cast<GLfloat>(i), static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest) * 128.0F);
}
glEnd();
// 不要在这里清 isHistogram:GLUT 常在菜单关闭后再重绘一次,
// 若此刻清掉标志,下一帧 glClear + glDrawPixels 就会把直方图覆盖掉。
glDisable(GL_HISTOGRAM);
}
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadIdentity(); // 重置颜色矩阵
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glDisable(GL_CONVOLUTION_2D);
glDisable(GL_COLOR_TABLE); // 禁用颜色表
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
// 截屏辅助:glReadPixels 同样会经过 pixel transfer,
// 构造时把映射与通道系数复位为恒等,析构时自动还原之前的状态。
class PixelTransferGuard {
public:
PixelTransferGuard() {
glGetBooleanv(GL_MAP_COLOR, &mapColor_);
glGetFloatv(GL_RED_SCALE, &redScale_);
glGetFloatv(GL_GREEN_SCALE, &greenScale_);
glGetFloatv(GL_BLUE_SCALE, &blueScale_);
glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
}
~PixelTransferGuard() {
glPixelTransferi(GL_MAP_COLOR, mapColor_ ? GL_TRUE : GL_FALSE);
glPixelTransferf(GL_RED_SCALE, redScale_);
glPixelTransferf(GL_GREEN_SCALE, greenScale_);
glPixelTransferf(GL_BLUE_SCALE, blueScale_);
}
PixelTransferGuard(const PixelTransferGuard&) = delete;
PixelTransferGuard& operator=(const PixelTransferGuard&) = delete;
private:
GLboolean mapColor_ = GL_FALSE;
GLfloat redScale_ = 1.0F;
GLfloat greenScale_ = 1.0F;
GLfloat blueScale_ = 1.0F;
};
// 截屏读取:把"从当前窗口前缓冲取像素"这件事收拢到一处。
// 构造时复位 pixel transfer、打开 PACK 对齐、切到 GL_FRONT;析构时自动还原读缓冲区。
class ScreenshotReader {
public:
ScreenshotReader() {
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetIntegerv(GL_READ_BUFFER, &previousBuffer_);
std::cout << std::format("Current read buffer: 0x{:X}", previousBuffer_) << std::endl;
glReadBuffer(GL_FRONT);
}
~ScreenshotReader() { glReadBuffer(previousBuffer_); }
ScreenshotReader(const ScreenshotReader&) = delete;
ScreenshotReader& operator=(const ScreenshotReader&) = delete;
private:
PixelTransferGuard transferGuard_; // 复位 pixel transfer(读完自动还原)
GLint previousBuffer_ = GL_BACK; // 读完把读缓冲区切回去
};
void readFrontBuffer(const int width, const int height, const GLenum format, GLbyte* out) {
const ScreenshotReader reader; // 读前准备 + 读完还原
glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, out);
}
void writePng(const std::string& filename, const int width, const int height) {
// openGL 的 glReadPixels 读取的图像是自下而上的,而 PNG 是自上而下的,所以需要翻转图像
stbi_flip_vertically_on_write(1);
const std::size_t imageSize = width * height * 3; // 3 bytes per pixel for RGB
const auto flippedImageData = std::make_unique<GLbyte[]>(imageSize);
readFrontBuffer(width, height, GL_RGB, flippedImageData.get()); // 像素在内存里是自下而上的
if (!stbi_write_png(filename.c_str(), width, height, 3, flippedImageData.get(), width * 3)) {
std::cerr << "Failed to write PNG file: " << filename << std::endl;
throw std::runtime_error("Failed to write PNG file");
}
}
void writeTarga(const std::string& filename, const TargaHeader& header) {
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize = newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
readFrontBuffer(newHeader.width, newHeader.height, GL_BGR, imageBuffer.get());
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
if (option >= 10) { // 10 及以上是"动作",与 0..9 的渲染模式分开,避免编号撞车
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
switch (option) {
case 10: writeTarga("output.tga", targaData.first); break;
case 11: writePng("output.png", viewport[2], viewport[3]); break;
default: break;
}
return; // 动作不改变当前渲染模式
}
renderMode = option;
isHistogram = (option == 6) ? GL_TRUE : GL_FALSE; // 只有直方图模式开启,切到其它模式自动关闭
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("暗化图片", 1);
glutAddMenuEntry("亮化图片", 2);
glutAddMenuEntry("反转图片", 3);
glutAddMenuEntry("浮雕化图片", 4);
glutAddMenuEntry("锐化图片", 5);
glutAddMenuEntry("直方图均衡化", 6);
glutAddMenuEntry("blur图片", 7);
glutAddMenuEntry("保存 TGA", 10);
glutAddMenuEntry("保存 PNG", 11);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
std::cout << "GL_ARB_imaging supported: " << glutExtensionSupported("GL_ARB_imaging") << std::endl;
glutMainLoop();
return 0;
}
高斯模糊
#ifdef __APPLE__
#include <OpenGL/glext.h>
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <array> // std::array(高斯核)
#include <format> // std::format
#include <stdexcept> // std::runtime_error
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 0;
static GLint mainWindowId = 0; // 主窗口 ID
static GLboolean isHistogram = GL_FALSE; // 是否显示直方图
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
void renderScene(){
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
// 光栅(Raster)位置设置
glRasterPos2i(0, 0);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(viewport[2] / static_cast<GLfloat>(targaData.first.width), viewport[3] / static_cast<GLfloat>(targaData.first.height)); // 设置像素缩放因子为默认值
static constexpr GLfloat lumMat[16] = {
0.30f, 0.30f, 0.30f, 0.0f,
0.59f, 0.59f, 0.59f, 0.0f,
0.11f, 0.11f, 0.11f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
if (isHistogram) {
glMatrixMode(GL_COLOR);
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE);
glEnable(GL_HISTOGRAM);
}
switch (renderMode) {
case 5: {
static constexpr GLfloat sharpen[3 * 3] = {
0.0f, -1.0f, 0.0f,
-1.0f, 5.0f, -1.0f,
0.0f, -1.0f, 0.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, sharpen);
glEnable(GL_CONVOLUTION_2D);
break;
}
case 4: {
static constexpr GLfloat emboss[3 * 3] = {
2.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, 0.0f, -1.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, emboss);
glEnable(GL_CONVOLUTION_2D);
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 3: { // 反转图像
// 反转表的生成与代理查询只做一次:它们是静态开销,每帧重复既浪费又会刷屏
static GLubyte invertTable[256][3]{};
static bool invertTableReady = false;
if (!invertTableReady) {
for (int i = 0; i < 256; ++i) {
invertTable[i][0] = static_cast<GLubyte>(255 - i);
invertTable[i][1] = static_cast<GLubyte>(255 - i);
invertTable[i][2] = static_cast<GLubyte>(255 - i);
}
glColorTable(GL_PROXY_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
GLint width = 0;
glGetColorTableParameteriv(GL_PROXY_COLOR_TABLE, GL_COLOR_TABLE_WIDTH, &width);
std::cout << "Proxy color table width: " << width << " (仅首次查询)" << std::endl;
invertTableReady = true;
}
glColorTable(GL_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, invertTable);
glEnable(GL_COLOR_TABLE);
break;
}
case 2: { // 亮化图片
glMatrixMode(GL_COLOR);
glScalef(1.25F, 1.25F, 1.25F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 1: { // 暗化图片
glMatrixMode(GL_COLOR);
glScalef(0.8F, 0.8F, 0.8F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 7: { // 模糊:可分离高斯模糊(横竖各一趟 1D)—— 调 kRadius / kSigma 改强度
constexpr int kRadius = 4; // 半径:4 → 1D 长度 9(越大越糊)
constexpr float kSigma = 5.5F; // 标准差:越大越糊(超过半径后趋于方波)
constexpr int kSize = kRadius * 2 + 1;
// 可分离滤波只需要一维权重(行、列共用同一组),按高斯公式生成并归一化
static const std::array<GLfloat, kSize> kernel1D = [] {
std::array<GLfloat, kSize> weights{};
GLfloat sum = 0.0F;
for (int i = -kRadius; i <= kRadius; ++i) {
const GLfloat w = std::exp(-static_cast<GLfloat>(i * i) /
(2.0F * kSigma * kSigma));
weights[i + kRadius] = w;
sum += w;
}
for (GLfloat& w : weights) {
w /= sum; // 归一化:总权重为 1,整体亮度不变
}
return weights;
}();
// 先横向、再纵向各做一次 1D 卷积,等价于 (2r+1)×(2r+1) 的二维高斯,
// 但每像素乘加从 kSize² = 81 降到 2×kSize = 18
// 注意:format/type 只有一对,row 与 column 共用
// 合法边界模式只有 GL_REDUCE(默认,输出会缩小)、GL_CONSTANT_BORDER、GL_REPLICATE_BORDER;
// 误用 GL_REPEAT 在 macOS 上不报错,但第二次绘制会段错误
glConvolutionParameteri(GL_CONVOLUTION_2D, GL_CONVOLUTION_BORDER_MODE, GL_REPLICATE_BORDER);
glConvolutionParameteri(GL_SEPARABLE_2D, GL_CONVOLUTION_BORDER_MODE, GL_REPLICATE_BORDER);
glSeparableFilter2D(GL_SEPARABLE_2D, GL_RGB, kSize, kSize,
GL_LUMINANCE, GL_FLOAT,
kernel1D.data(), // row:水平权重
kernel1D.data()); // column:垂直权重
glEnable(GL_SEPARABLE_2D);
break;
}
default:
// 默认渲染模式的处理逻辑
break;
}
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
if (isHistogram) {
std::cout << "Displaying histogram..." << std::endl;
static GLint histogram[256];
glGetHistogram(GL_HISTOGRAM, GL_TRUE, GL_LUMINANCE, GL_INT, histogram);
GLint largest = 0;
for(GLint i = 0; i < 256; ++i) {
if (histogram[i] > largest) {
largest = histogram[i];
}
}
glColor3f(1.0F, 1.0F, 1.0F);
glBegin(GL_LINE_STRIP);
for (GLint i = 0; i < 256; ++i) {
// glVertex2f(static_cast<GLfloat>(i) / 255.0F, static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest));
glVertex2f(static_cast<GLfloat>(i), static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest) * 128.0F);
}
glEnd();
// 不要在这里清 isHistogram:GLUT 常在菜单关闭后再重绘一次,
// 若此刻清掉标志,下一帧 glClear + glDrawPixels 就会把直方图覆盖掉。
glDisable(GL_HISTOGRAM);
}
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadIdentity(); // 重置颜色矩阵
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glDisable(GL_CONVOLUTION_2D);
glDisable(GL_SEPARABLE_2D); // 可分离滤波是独立的 enable 位,必须一起关
glDisable(GL_COLOR_TABLE); // 禁用颜色表
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
// 截屏辅助:glReadPixels 同样会经过 pixel transfer,
// 构造时把映射与通道系数复位为恒等,析构时自动还原之前的状态。
class PixelTransferGuard {
public:
PixelTransferGuard() {
glGetBooleanv(GL_MAP_COLOR, &mapColor_);
glGetFloatv(GL_RED_SCALE, &redScale_);
glGetFloatv(GL_GREEN_SCALE, &greenScale_);
glGetFloatv(GL_BLUE_SCALE, &blueScale_);
glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
}
~PixelTransferGuard() {
glPixelTransferi(GL_MAP_COLOR, mapColor_ ? GL_TRUE : GL_FALSE);
glPixelTransferf(GL_RED_SCALE, redScale_);
glPixelTransferf(GL_GREEN_SCALE, greenScale_);
glPixelTransferf(GL_BLUE_SCALE, blueScale_);
}
PixelTransferGuard(const PixelTransferGuard&) = delete;
PixelTransferGuard& operator=(const PixelTransferGuard&) = delete;
private:
GLboolean mapColor_ = GL_FALSE;
GLfloat redScale_ = 1.0F;
GLfloat greenScale_ = 1.0F;
GLfloat blueScale_ = 1.0F;
};
// 截屏读取:把"从当前窗口前缓冲取像素"这件事收拢到一处。
// 构造时复位 pixel transfer、打开 PACK 对齐、切到 GL_FRONT;析构时自动还原读缓冲区。
class ScreenshotReader {
public:
ScreenshotReader() {
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetIntegerv(GL_READ_BUFFER, &previousBuffer_);
std::cout << std::format("Current read buffer: 0x{:X}", previousBuffer_) << std::endl;
glReadBuffer(GL_FRONT);
}
~ScreenshotReader() { glReadBuffer(previousBuffer_); }
ScreenshotReader(const ScreenshotReader&) = delete;
ScreenshotReader& operator=(const ScreenshotReader&) = delete;
private:
PixelTransferGuard transferGuard_; // 复位 pixel transfer(读完自动还原)
GLint previousBuffer_ = GL_BACK; // 读完把读缓冲区切回去
};
void readFrontBuffer(const int width, const int height, const GLenum format, GLbyte* out) {
const ScreenshotReader reader; // 读前准备 + 读完还原
glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, out);
}
void writePng(const std::string& filename, const int width, const int height) {
// openGL 的 glReadPixels 读取的图像是自下而上的,而 PNG 是自上而下的,所以需要翻转图像
stbi_flip_vertically_on_write(1);
const std::size_t imageSize = width * height * 3; // 3 bytes per pixel for RGB
const auto flippedImageData = std::make_unique<GLbyte[]>(imageSize);
readFrontBuffer(width, height, GL_RGB, flippedImageData.get()); // 像素在内存里是自下而上的
if (!stbi_write_png(filename.c_str(), width, height, 3, flippedImageData.get(), width * 3)) {
std::cerr << "Failed to write PNG file: " << filename << std::endl;
throw std::runtime_error("Failed to write PNG file");
}
}
void writeTarga(const std::string& filename, const TargaHeader& header) {
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize = newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
readFrontBuffer(newHeader.width, newHeader.height, GL_BGR, imageBuffer.get());
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
if (option >= 10) { // 10 及以上是"动作",与 0..9 的渲染模式分开,避免编号撞车
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
switch (option) {
case 10: writeTarga("output.tga", targaData.first); break;
case 11: writePng("output.png", viewport[2], viewport[3]); break;
default: break;
}
return; // 动作不改变当前渲染模式
}
renderMode = option;
isHistogram = (option == 6) ? GL_TRUE : GL_FALSE; // 只有直方图模式开启,切到其它模式自动关闭
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("暗化图片", 1);
glutAddMenuEntry("亮化图片", 2);
glutAddMenuEntry("反转图片", 3);
glutAddMenuEntry("浮雕化图片", 4);
glutAddMenuEntry("锐化图片", 5);
glutAddMenuEntry("直方图均衡化", 6);
glutAddMenuEntry("blur图片", 7);
glutAddMenuEntry("保存 TGA", 10);
glutAddMenuEntry("保存 PNG", 11);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
std::cout << "GL_ARB_imaging supported: " << glutExtensionSupported("GL_ARB_imaging") << std::endl;
glutMainLoop();
return 0;
}
几种边界模式
#ifdef __APPLE__
#include <OpenGL/glext.h>
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
#include <ostream>
#include <numbers>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <array> // std::array(高斯核)
#include <format> // std::format
#include <stdexcept> // std::runtime_error
#include <memory> // std::unique_ptr / std::make_unique
#include <utility> // std::pair / std::make_pair / std::move
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
static GLint renderMode = 0;
static GLint mainWindowId = 0; // 主窗口 ID
static GLboolean isHistogram = GL_FALSE; // 是否显示直方图
// Targa 图像文件头结构体
#pragma pack(push, 1) // 关闭结构体对齐填充
struct TargaHeader {
// 图像 ID 区长度,0 = 无
GLubyte idLength;
GLubyte colorMapType; // 0 = 无调色板
GLubyte dataTypeCode; // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
GLushort colorMapOrigin; // 调色板起始索引
GLushort colorMapLength; // 调色板颜色的数量
GLubyte colorMapDepth; // 调色板每条颜色的位数
GLushort xOrigin; // 图像在屏幕上的 X 坐标
GLushort yOrigin; // 图像在屏幕上的 Y 坐标
GLushort width; // 图像宽度
GLushort height; // 图像高度
// 每像素 24 位 = RGB 各 8 位
GLubyte bitsPerPixel;
// 0 表示自下而上存储
GLubyte imageDescriptor;
};
#pragma pack(pop) // 恢复结构体对齐填充
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
os << "TargaHeader { "
<< "idLength=" << static_cast<int>(header.idLength) << ", "
<< "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
<< "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
<< "colorMapOrigin=" << header.colorMapOrigin << ", "
<< "colorMapLength=" << header.colorMapLength << ", "
<< "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
<< "xOrigin=" << header.xOrigin << ", "
<< "yOrigin=" << header.yOrigin << ", "
<< "width=" << header.width << ", "
<< "height=" << header.height << ", "
<< "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
<< "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
<< " }";
return os;
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>>;
const static std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> targaData = loadTarga();
void changeSize(const int width, const int height){
std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
if(height == 0) {
throw std::runtime_error("Height cannot be zero.");
}
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
switch (key) {
case 27: // ESC 键
case 'q':
case 'Q':
std::cout << "keyboard close..." << "\n";
exit(0);
break;
case 'a':
case 'A':
break;
case 'd':
case 'D':
break;
default:
break;
}
}
void specialKeys(const int key, const int x, const int y){
switch (key) {
case GLUT_KEY_UP:
break;
case GLUT_KEY_DOWN:
break;
case GLUT_KEY_LEFT:
break;
case GLUT_KEY_RIGHT:
break;
default:
return; // 不处理其他按键
}
}
auto loadTarga() -> std::pair<TargaHeader, std::unique_ptr<GLbyte[]>> {
std::cout << "Loading Targa image..." << std::endl;
std::ifstream file("../horse.tga", std::ios::binary);
if (!file) {
std::cerr << "Failed to open horse.tga" << std::endl;
throw std::runtime_error("Failed to open horse.tga");
}
TargaHeader header;
std::cout << "Size of TargaHeader: " << sizeof(TargaHeader) << std::endl;
file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to read Targa header" << std::endl;
throw std::runtime_error("Failed to read Targa header");
}
std::cout << "Targa header: " << header << std::endl;
const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8);
auto imageData = std::make_unique<GLbyte[]>(imageSize); // 注意:不能是 const auto,unique_ptr 需要可移动
file.read(reinterpret_cast<char*>(imageData.get()), imageSize);
if (!file) {
std::cerr << "Failed to read Targa image data" << std::endl;
throw std::runtime_error("Failed to read Targa image data");
}
std::cout << "Successfully read Targa image data" << std::endl;
return std::make_pair(std::move(header), std::move(imageData));
}
// CONSTANT 边界需要一个"边框常量色"。这里取图像四条边的平均色:
// 边界像素向外融合时朝"自身边缘的平均色"过渡,而不是被默认的黑色压暗。
// 注意:targaData 的内存顺序是 BGR(绘制时用 GL_BGR),取用时要把 B、R 换回来。
std::array<GLfloat, 4> borderAverageColor() {
static const std::array<GLfloat, 4> cached = [] {
const int w = targaData.first.width;
const int h = targaData.first.height;
const auto* px = reinterpret_cast<const unsigned char*>(targaData.second.get());
auto sample = [&](const int x, const int y) {
const std::size_t i = (static_cast<std::size_t>(y) * w + x) * 3;
return std::array<double, 3>{
static_cast<double>(px[i + 2]), // 数据是 BGR → 换回 R
static_cast<double>(px[i + 1]), // G
static_cast<double>(px[i]), // B
};
};
std::array<double, 3> sum{0.0, 0.0, 0.0};
long count = 0;
for (int x = 0; x < w; ++x) {
for (const int y : {0, h - 1}) {
const auto c = sample(x, y);
for (int k = 0; k < 3; ++k) sum[k] += c[k];
++count;
}
}
for (int y = 0; y < h; ++y) {
for (const int x : {0, w - 1}) {
const auto c = sample(x, y);
for (int k = 0; k < 3; ++k) sum[k] += c[k];
++count;
}
}
return std::array<GLfloat, 4>{
static_cast<GLfloat>(sum[0] / count / 255.0),
static_cast<GLfloat>(sum[1] / count / 255.0),
static_cast<GLfloat>(sum[2] / count / 255.0),
1.0F,
};
}();
return cached;
}
void renderScene(){
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
// 光栅(Raster)位置设置
glRasterPos2i(0, 0);
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glPixelZoom(viewport[2] / static_cast<GLfloat>(targaData.first.width), viewport[3] / static_cast<GLfloat>(targaData.first.height)); // 设置像素缩放因子为默认值
static constexpr GLfloat lumMat[16] = {
0.30f, 0.30f, 0.30f, 0.0f,
0.59f, 0.59f, 0.59f, 0.0f,
0.11f, 0.11f, 0.11f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
if (isHistogram) {
glMatrixMode(GL_COLOR);
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glHistogram(GL_HISTOGRAM, 256, GL_LUMINANCE, GL_FALSE);
glEnable(GL_HISTOGRAM);
}
switch (renderMode) {
case 5: {
static constexpr GLfloat sharpen[3 * 3] = {
0.0f, -1.0f, 0.0f,
-1.0f, 5.0f, -1.0f,
0.0f, -1.0f, 0.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, sharpen);
glEnable(GL_CONVOLUTION_2D);
break;
}
case 4: {
static constexpr GLfloat emboss[3 * 3] = {
2.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f,
0.0f, 0.0f, -1.0f
};
glConvolutionFilter2D(GL_CONVOLUTION_2D, GL_RGB, 3, 3, GL_LUMINANCE, GL_FLOAT, emboss);
glEnable(GL_CONVOLUTION_2D);
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadMatrixf(lumMat);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 3: { // 反转图像
// 反转表的生成与代理查询只做一次:它们是静态开销,每帧重复既浪费又会刷屏
static GLubyte invertTable[256][3]{};
static bool invertTableReady = false;
if (!invertTableReady) {
for (int i = 0; i < 256; ++i) {
invertTable[i][0] = static_cast<GLubyte>(255 - i);
invertTable[i][1] = static_cast<GLubyte>(255 - i);
invertTable[i][2] = static_cast<GLubyte>(255 - i);
}
glColorTable(GL_PROXY_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
GLint width = 0;
glGetColorTableParameteriv(GL_PROXY_COLOR_TABLE, GL_COLOR_TABLE_WIDTH, &width);
std::cout << "Proxy color table width: " << width << " (仅首次查询)" << std::endl;
invertTableReady = true;
}
glColorTable(GL_COLOR_TABLE, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, invertTable);
glEnable(GL_COLOR_TABLE);
break;
}
case 2: { // 亮化图片
glMatrixMode(GL_COLOR);
glScalef(1.25F, 1.25F, 1.25F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 1: { // 暗化图片
glMatrixMode(GL_COLOR);
glScalef(0.8F, 0.8F, 0.8F);
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
break;
}
case 7: { // 模糊:可分离高斯模糊(横竖各一趟 1D)—— 调 kRadius / kSigma 改强度
/*
kRadius = 1
0.3679 0.6065 0.3679
0.6065 1.0000 0.6065
0.3679 0.6065 0.3679
// 归一化,为高斯卷积核
0.0751 0.1238 0.0751
0.1238 0.2042 0.1238
0.0751 0.1238 0.0751
*/
// kRadius: 高斯卷积核半径, kRadius = 3 * kSigma
constexpr int kRadius = 5; // 半径:4 → 1D 长度 9(越大越糊)
constexpr float kSigma = kRadius == 1 ? 0.7071F : static_cast<float>(kRadius) / 3.0F; // 标准差:越大越糊(超过半径后趋于方波)
constexpr int kSize = kRadius * 2 + 1;
/*
w(i) = e^(−i²/(2σ²))
w(i) = w(i) / Σ w(j)
*/
// 可分离滤波只需要一维权重(行、列共用同一组),按高斯公式生成并归一化
static const std::array<GLfloat, kSize> kernel1D = [] {
std::array<GLfloat, kSize> weights{};
GLfloat sum = 0.0F;
for (int i = -kRadius; i <= kRadius; ++i) {
const GLfloat w = std::exp(-static_cast<GLfloat>(i * i) /
(2.0F * kSigma * kSigma));
weights[i + kRadius] = w;
sum += w;
}
for (GLfloat& w : weights) {
w /= sum; // 归一化:总权重为 1,整体亮度不变
}
return weights;
}();
// 矩阵乘法 kernel1D(列)* kernel1D(行) = 2D 高斯卷积核
// 先横向、再纵向各做一次 1D 卷积,等价于 (2r+1)×(2r+1) 的二维高斯,
// 但每像素乘加从 kSize² = 81 降到 2×kSize = 18
// 注意:format/type 只有一对,row 与 column 共用
// 合法边界模式只有 GL_REDUCE(默认,输出会缩小)、GL_CONSTANT_BORDER、GL_REPLICATE_BORDER;
// 误用 GL_REPEAT 在 macOS 上不报错,但第二次绘制会段错误
// 边界模式是"枚举",必须用 glConvolutionParameteri;颜色是"浮点数组",用 glConvolutionParameterfv
// CONSTANT:图像外按一个常量色参与卷积 —— 尺寸不变、边界也参与模糊
const std::array<GLfloat, 4> borderColor = borderAverageColor();
std::cout << std::format("CONSTANT 边框色 = ({:.3F}, {:.3F}, {:.3F})\n",
borderColor[0], borderColor[1], borderColor[2]);
glConvolutionParameteri(GL_CONVOLUTION_2D, GL_CONVOLUTION_BORDER_MODE, GL_CONSTANT_BORDER);
glConvolutionParameteri(GL_SEPARABLE_2D, GL_CONVOLUTION_BORDER_MODE, GL_CONSTANT_BORDER);
glConvolutionParameterfv(GL_CONVOLUTION_2D, GL_CONVOLUTION_BORDER_COLOR, borderColor.data());
glConvolutionParameterfv(GL_SEPARABLE_2D, GL_CONVOLUTION_BORDER_COLOR, borderColor.data());
glSeparableFilter2D(GL_SEPARABLE_2D, GL_RGB, kSize, kSize,
GL_LUMINANCE, GL_FLOAT,
kernel1D.data(), // row:水平权重
kernel1D.data()); // column:垂直权重
glEnable(GL_SEPARABLE_2D);
break;
}
default:
// 默认渲染模式的处理逻辑
break;
}
glDrawPixels(targaData.first.width, targaData.first.height, GL_BGR, GL_UNSIGNED_BYTE, targaData.second.get());
if (isHistogram) {
std::cout << "Displaying histogram..." << std::endl;
static GLint histogram[256];
glGetHistogram(GL_HISTOGRAM, GL_TRUE, GL_LUMINANCE, GL_INT, histogram);
GLint largest = 0;
for(GLint i = 0; i < 256; ++i) {
if (histogram[i] > largest) {
largest = histogram[i];
}
}
glColor3f(1.0F, 1.0F, 1.0F);
glBegin(GL_LINE_STRIP);
for (GLint i = 0; i < 256; ++i) {
// glVertex2f(static_cast<GLfloat>(i) / 255.0F, static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest));
glVertex2f(static_cast<GLfloat>(i), static_cast<GLfloat>(histogram[i]) / static_cast<GLfloat>(largest) * 128.0F);
}
glEnd();
// 不要在这里清 isHistogram:GLUT 常在菜单关闭后再重绘一次,
// 若此刻清掉标志,下一帧 glClear + glDrawPixels 就会把直方图覆盖掉。
glDisable(GL_HISTOGRAM);
}
glMatrixMode(GL_COLOR); // 恢复模型视图矩阵模式
glLoadIdentity(); // 重置颜色矩阵
glMatrixMode(GL_MODELVIEW); // 恢复模型视图矩阵模式
glDisable(GL_CONVOLUTION_2D);
glDisable(GL_SEPARABLE_2D); // 可分离滤波是独立的 enable 位,必须一起关
glDisable(GL_COLOR_TABLE); // 禁用颜色表
glutSwapBuffers();
}
void setupRc(){
glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
// 截屏辅助:glReadPixels 同样会经过 pixel transfer,
// 构造时把映射与通道系数复位为恒等,析构时自动还原之前的状态。
class PixelTransferGuard {
public:
PixelTransferGuard() {
glGetBooleanv(GL_MAP_COLOR, &mapColor_);
glGetFloatv(GL_RED_SCALE, &redScale_);
glGetFloatv(GL_GREEN_SCALE, &greenScale_);
glGetFloatv(GL_BLUE_SCALE, &blueScale_);
glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
glPixelTransferf(GL_RED_SCALE, 1.0F);
glPixelTransferf(GL_GREEN_SCALE, 1.0F);
glPixelTransferf(GL_BLUE_SCALE, 1.0F);
}
~PixelTransferGuard() {
glPixelTransferi(GL_MAP_COLOR, mapColor_ ? GL_TRUE : GL_FALSE);
glPixelTransferf(GL_RED_SCALE, redScale_);
glPixelTransferf(GL_GREEN_SCALE, greenScale_);
glPixelTransferf(GL_BLUE_SCALE, blueScale_);
}
PixelTransferGuard(const PixelTransferGuard&) = delete;
PixelTransferGuard& operator=(const PixelTransferGuard&) = delete;
private:
GLboolean mapColor_ = GL_FALSE;
GLfloat redScale_ = 1.0F;
GLfloat greenScale_ = 1.0F;
GLfloat blueScale_ = 1.0F;
};
// 截屏读取:把"从当前窗口前缓冲取像素"这件事收拢到一处。
// 构造时复位 pixel transfer、打开 PACK 对齐、切到 GL_FRONT;析构时自动还原读缓冲区。
class ScreenshotReader {
public:
ScreenshotReader() {
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glGetIntegerv(GL_READ_BUFFER, &previousBuffer_);
std::cout << std::format("Current read buffer: 0x{:X}", previousBuffer_) << std::endl;
glReadBuffer(GL_FRONT);
}
~ScreenshotReader() { glReadBuffer(previousBuffer_); }
ScreenshotReader(const ScreenshotReader&) = delete;
ScreenshotReader& operator=(const ScreenshotReader&) = delete;
private:
PixelTransferGuard transferGuard_; // 复位 pixel transfer(读完自动还原)
GLint previousBuffer_ = GL_BACK; // 读完把读缓冲区切回去
};
void readFrontBuffer(const int width, const int height, const GLenum format, GLbyte* out) {
const ScreenshotReader reader; // 读前准备 + 读完还原
glReadPixels(0, 0, width, height, format, GL_UNSIGNED_BYTE, out);
}
void writePng(const std::string& filename, const int width, const int height) {
// openGL 的 glReadPixels 读取的图像是自下而上的,而 PNG 是自上而下的,所以需要翻转图像
stbi_flip_vertically_on_write(1);
const std::size_t imageSize = width * height * 3; // 3 bytes per pixel for RGB
const auto flippedImageData = std::make_unique<GLbyte[]>(imageSize);
readFrontBuffer(width, height, GL_RGB, flippedImageData.get()); // 像素在内存里是自下而上的
if (!stbi_write_png(filename.c_str(), width, height, 3, flippedImageData.get(), width * 3)) {
std::cerr << "Failed to write PNG file: " << filename << std::endl;
throw std::runtime_error("Failed to write PNG file");
}
}
void writeTarga(const std::string& filename, const TargaHeader& header) {
// 获取viewport的宽高
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
std::cout << std::format("Viewport: x={}, y={}, width={}, height={}", viewport[0], viewport[1], viewport[2], viewport[3]) << std::endl;
const GLushort width = viewport[2];
const GLushort height = viewport[3];
const TargaHeader newHeader = {
.idLength = header.idLength,
.colorMapType = header.colorMapType,
.dataTypeCode = header.dataTypeCode,
.colorMapOrigin = header.colorMapOrigin,
.colorMapLength = header.colorMapLength,
.colorMapDepth = header.colorMapDepth,
.xOrigin = header.xOrigin,
.yOrigin = header.yOrigin,
.width = width,
.height = height,
.bitsPerPixel = header.bitsPerPixel,
.imageDescriptor = header.imageDescriptor,
};
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open " << filename << " for writing" << std::endl;
throw std::runtime_error("Failed to open file for writing");
}
file.write(reinterpret_cast<const char*>(&newHeader), sizeof(TargaHeader));
if (!file) {
std::cerr << "Failed to write Targa header to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa header");
}
const std::size_t imageSize = newHeader.width * newHeader.height * (newHeader.bitsPerPixel / 8);
const std::unique_ptr<GLbyte[]> imageBuffer = std::make_unique<GLbyte[]>(imageSize);
readFrontBuffer(newHeader.width, newHeader.height, GL_BGR, imageBuffer.get());
file.write(reinterpret_cast<const char*>(imageBuffer.get()), imageSize);
if (!file) {
std::cerr << "Failed to write Targa image data to " << filename << std::endl;
throw std::runtime_error("Failed to write Targa image data");
}
}
void processMenu(const int option){
std::cout << std::format("Menu option selected: {}", option) << std::endl;
glutSetWindow(mainWindowId);
if (option >= 10) { // 10 及以上是"动作",与 0..9 的渲染模式分开,避免编号撞车
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
switch (option) {
case 10: writeTarga("output.tga", targaData.first); break;
case 11: writePng("output.png", viewport[2], viewport[3]); break;
default: break;
}
return; // 动作不改变当前渲染模式
}
renderMode = option;
isHistogram = (option == 6) ? GL_TRUE : GL_FALSE; // 只有直方图模式开启,切到其它模式自动关闭
glutPostRedisplay();
}
auto main(const int argc, const char** argv) -> int {
glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800, 600);
mainWindowId = glutCreateWindow("OpenGL Window");
std::cout << "Main window ID: " << mainWindowId << std::endl;
// 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
GLint sampleBuffers = 0, samples = 0;
glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
glGetIntegerv(GL_SAMPLES, &samples);
std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
std::cout << "current path: " << std::filesystem::current_path() << std::endl;
glutReshapeFunc(changeSize);
glutKeyboardFunc(keyboard);
glutSpecialFunc(specialKeys);
glutDisplayFunc(renderScene);
glutCreateMenu(processMenu);
glutAddMenuEntry("重置", 0);
glutAddMenuEntry("暗化图片", 1);
glutAddMenuEntry("亮化图片", 2);
glutAddMenuEntry("反转图片", 3);
glutAddMenuEntry("浮雕化图片", 4);
glutAddMenuEntry("锐化图片", 5);
glutAddMenuEntry("直方图均衡化", 6);
glutAddMenuEntry("blur图片", 7);
glutAddMenuEntry("保存 TGA", 10);
glutAddMenuEntry("保存 PNG", 11);
glutAttachMenu(GLUT_RIGHT_BUTTON);
setupRc();
std::cout << "GL_ARB_imaging supported: " << glutExtensionSupported("GL_ARB_imaging") << std::endl;
glutMainLoop();
return 0;
}

浙公网安备 33010602011771号