[TinyRenderer] Chapter1 p1 Output Image

由于本文章是对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,里面仅是一些头文件。

输出结果

[TinyRenderer] Chapter1 p1 Output Image

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

发表评论

相关文章