由于本文章是对TinyRenderer的模仿,所以并不打算引入外部库。

那么我们第一步需要解决的就是图形输出的问题,毕竟,如果连渲染的结果都看不到,那还叫什么Renderer嘛。

由于不引入外部库,所以选择输出的图片格式应该越简单越好,各种位图就成为了我们的首选。

这里我们选择了生态较好的bmp位图。

技术上,由于只使用C++,所以各种文件流就成了我们构建图片的唯一工具。

本章目标

输出一张保存了我们渲染结果的bmp位图

需求:

  • 大小可以控制,也就是位图的尺寸可控
  • 控制某个像素点的颜色,精准更改set()
  • 对位图进行上下反转

实现

BMPImage.h

#ifndef BMP_IMAGE_H
#define BMP_IMAGE_H
#include <string>
#include <vector> #pragma pack(push, 1)
struct BMPFileHeader
{
uint16_t bfType; // BMP文件的类型,必须为"B"然后是"M"
uint32_t bfSize; // 文件大小
uint16_t bfReserved1; // 保留字,必须为0
uint16_t bfReserved2; // 从文件头到实际位图数据的偏移字节数
uint32_t bfOffBits; // 信息头的大小
}; struct BMPInfoHeader
{
uint32_t biSize; // info head size
int32_t biWidth; // 图像宽度
int32_t biHeight; // 图像高度
uint16_t biPlanes; // 图像的位面数
uint16_t biBitCount; // 每个像素的位数
uint32_t biCompression; // 压缩类型
uint32_t biSizeImage; // 图像的大小,以字节为单位
int32_t biXPelsPerMeter; // 水平分辨率
int32_t biYPelsPerMeter; // 垂直分辨率
uint32_t biClrUsed; // 位图实际使用的颜色表中的颜色数
uint32_t biClrImportant; // 位图显示过程中重要的颜色数
};
#pragma pack(pop) /**
* \brief custom the color format used
*/
enum ColorFormat
{
RGB,
CMYK
}; struct RGBPixel
{
uint8_t red;
uint8_t green;
uint8_t blue;
RGBPixel() : red(0), green(0), blue(0)
{
}
RGBPixel(uint8_t red, uint8_t green, uint8_t blue) : red(red), green(green), blue(blue)
{
}
}; class BMPImage
{
public:
BMPImage() = delete;
BMPImage(unsigned int width, unsigned int height, ColorFormat colorFormat = ColorFormat::RGB);
void loadData(std::vector<char>&& userData);
void generate(const std::string& fileName);
void loadDataAndGenerate(std::vector<char>&& userData, const std::string& fileName); void set(int x, int y, RGBPixel pixel);
void flipVertically(); private:
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader; ColorFormat colorFormat;
std::vector<unsigned char> pixelData;
}; #endif

Important:

  • 在组织bmp文件头的部分,一定要使用预处理宏#pragma pack(push, 1)#pragma pack(pop),控制内存对齐方式为单字节,否则会由于编译器控制的内存对齐而导致文件格式错误,从而不能正确输出

BMPImage.cpp

#include "TinyRenderer/BMPImage.h"

#include <fstream>
#include <iostream> BMPImage::BMPImage(unsigned width, unsigned height, ColorFormat colorFormat)
{
int rowSize = (width * 3 + 3) & (~3); // Ensure row size is a multiple of 4 bytes
int fileSize = rowSize * height + sizeof(BMPFileHeader) + sizeof(BMPInfoHeader); // Set BMP file header
fileHeader.bfType = 0x4D42; // 'BM'
fileHeader.bfSize = fileSize;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader); // Set BMP info header
infoHeader.biSize = sizeof(BMPInfoHeader);
infoHeader.biWidth = width;
infoHeader.biHeight = height;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = 0;
infoHeader.biSizeImage = rowSize * height;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0; // Initialize pixel data
pixelData.resize(rowSize * height, 0);
} // not important now
void BMPImage::loadData(std::vector<char>&& userData)
{
// TODO: load image
} void BMPImage::generate(const std::string& fileName)
{
std::ofstream file(fileName, std::ios::out | std::ios::binary);
if (!file)
{
std::cerr << "Error: Unable to open file for writing." << std::endl;
return;
} // Write headers
file.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
file.write(reinterpret_cast<const char*>(&infoHeader), sizeof(infoHeader)); // Write pixel data
file.write(reinterpret_cast<const char*>(pixelData.data()), pixelData.size()); file.close();
} void BMPImage::loadDataAndGenerate(std::vector<char>&& userData, const std::string& fileName)
{
} void BMPImage::set(int x, int y, RGBPixel pixel)
{
if (x < 0 || y < 0 || x >= infoHeader.biWidth || y >= infoHeader.biHeight)
{
throw std::out_of_range("Pixel coordinates are out of bounds");
}
int rowSize = (infoHeader.biWidth * 3 + 3) & (~3);
int index = (infoHeader.biHeight - 1 - y) * rowSize + x * 3;
pixelData[index] = pixel.blue;
pixelData[index + 1] = pixel.green;
pixelData[index + 2] = pixel.red;
} void BMPImage::flipVertically()
{
int width = infoHeader.biWidth;
int height = infoHeader.biHeight;
int rowSize = (width * 3 + 3) & (~3); for (int y = 0; y < height / 2; ++y)
{
int topIndex = y * rowSize;
int bottomIndex = (height - 1 - y) * rowSize;
for (int x = 0; x < rowSize; ++x)
{
std::swap(pixelData[topIndex + x], pixelData[bottomIndex + x]);
}
}
}

测试

main.cpp

#include "TinyRenderer/TinyRenderer.h"
#include "TinyRenderer/BMPImage.h" int main()
{
BMPImage image(100, 100, ColorFormat::RGB);
RGBPixel white(255, 255, 255);
image.set(22, 77, white);
image.flipVertically();
image.generate("test.bmp");
std::cout << "Image Generated." << std::endl;
return 0;
}

请忽略TinyRenderer/TinyRenderer.h,里面仅是一些头文件。

输出结果

你能看到那个白点吗?那是我们的起点。

Chapter1 p1 Output Image的更多相关文章

  1. sql分页操作

    看到了网上关于分页的讲解  对最快的分页语句做了测试 还别说速度真快 总共6w条数据 速度确实so 快 前提是id是主键 或者是索引 declare @page int;--页数 declare @P ...

  2. C#解析json文件的方法

    C# 解析 json JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集. JSON采用完全独立于语言的 ...

  3. 设计一个用于人事管理的People(人员)类

    #include <iostream> #include <string> using namespace std; class Date //日期类 { private: i ...

  4. JSON学习

    1.JSON 语法是 JavaScript 对象表示语法的子集. l  数据在名称/值对中 l  数据由逗号分隔 l  花括号保存对象 l  方括号保存数组 JSON 值可以是: l  数字(整数或浮 ...

  5. js的this和面向对象编程

    很奇怪的是很多书或资料没有把这个事情讲清楚. 关键就是在于没有一个整体的思维技术模式,问题被隔离了所以反而不容易理解. 我们先看this,这是js的关键字,指示函数的上下文对象. 这里问题就来了,比如 ...

  6. oracle,mysql,SqlServer三种数据库的分页查询的实例。

    MySql: MySQL数据库实现分页比较简单,提供了 LIMIT函数.一般只需要直接写到sql语句后面就行了.LIMIT子 句可以用来限制由SELECT语句返回过来的数据数量,它有一个或两个参数,如 ...

  7. C# 解析JSON格式数据

    JSON简介 JSON(全称为JavaScript ObjectNotation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集.JSON采用完全独立于语言的文本格式,可 ...

  8. C# 解析 Json数据

    JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集. JSON采用完全独立于语言的文本格式,可以很容易在 ...

  9. C# 解析 Json

    C# 解析 json JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式.它是基于JavaScript语法标准的一个子集. JSON采用完全独立于语言的 ...

  10. sql server 2000数据库 最近经常出现某进程一直占用资源,阻塞?死锁?

    OA的数据库最近多次出现某进程一直占用资源,导致其他进程无法执行.使用sp_who2 和 sql server profiler跟踪查询,发现有以下几个语句常常占用资源: 1.declare @P1 ...

随机推荐

  1. 企业实施定制鞋厂ERP软件需要注意哪些问题?

    企业实施定制ERP软件是个复杂的管理系统工程,为了成功地为企业定制实施ERP软件,需要注意和解决几个关键的问题: (1) . 确立ERP系统实施和定制的决策者: (2) . 做好前期咨询与调研工作: ...

  2. 深度解密|基于 eBPF 的 Kubernetes 问题排查全景图发布

    ​简介:通过 eBPF 无侵入地采集多语言.多网络协议的黄金指标/网络指标/Trace,通过关联 Kubernetes 对象.应用.云服务等各种上下文,同时在需要进一步下钻的时候提供专业化的监测工具( ...

  3. 谈AK管理之基础篇 - 如何进行访问密钥的全生命周期管理?

    简介: 我们也常有听说例如AK被外部攻击者恶意获取,或者员工无心从github泄露的案例,最终导致安全事故或生产事故的发生.AK的应用场景极为广泛,因此做好AK的管理和治理就尤为重要了.本文将通过两种 ...

  4. 双引擎驱动Quick BI十亿数据0.3秒分析,首屏展示时间缩短30%

    ​简介:在规划中,Quick BI制定了产品竞争力建设的三大方向,包括Quick(快)能力.移动端能力和集成能力.针对其中的产品"报表查看打开慢""报表开发数据同步慢&q ...

  5. 官宣|Apache Flink 1.13.0 正式发布,流处理应用更加简单高效!

    简介: Flink 1.13.0 版本让流处理应用的使用像普通应用一样简单和自然,并且让用户可以更好地理解流作业的性能. ​翻译 | 高赟Review | 朱翥.马国维 Flink 1.13 发布了! ...

  6. 案例|自建or现成工具?小型创业团队敏捷研发探索

    简介: 实践和踩坑建议. 我是刘永良,是一名全栈开发者也是一名创业者,来自济南--一个目前被称为互联网洼地的地方.2020年4月和三位志同道合的朋友,在济南共同创建了山东旷野网络科技有限公司,主要从事 ...

  7. Forrester云原生开发者洞察白皮书,低代码概念缔造者又提出新的开发范式

    简介: 云原生时代的到来为开发者群体带来了前所未有的机遇,让开发者可以更加专注业务价值创造与创新,并使得人人成为开发者成为现实.广大开发者如何转型成为云原生开发者?运维等专业人员在云原生时代如何避免边 ...

  8. [Py] Python 接口数据用 pandas 高效写入 csv

    通过 pandas 把 dict 数据封装,调用接口方法写入 csv 文件. import pandas as pd data = [{"name": "a"} ...

  9. selenium操作浏览器模块

    selenium模块用途 selenuim原先多用于测试部门测试,由于它可以操作浏览器,有时候也用于爬虫领域 优点:操作浏览器访问网站 缺点:速度较慢 下载模块 # 下载模块 pip3 install ...

  10. SpringBoot配置两个一样的Bean,区分两个配置类——@Primary

    1.@Primary 作用: 指定默认bean. 当没有根据名字显示要注入哪个bean的时候,默认使用打了@Primary标签的bean 2.配置两个一样的bean @Configuration pu ...