Chapter1 p1 Output Image

chapter1,p1,output,image · 浏览次数 : 0

小编点评

本文主要讨论了一个名为TinyRenderer的渲染器的简易版本实现,重点在于图像的输出功能。 **主要内容概述如下**: 1. **[引言]**: 引言部分提到了本项目旨在模拟一个渲染器的某些功能,因此我们需要首先解决的是图形输出问题。鉴于不打算引入外部库,选择了简单易用的bmp位图作为输出格式,并介绍了位图的基本概念。 2. **[BMPImage类定义及结构]**: `BMPImage`类被定义为包含BMP文件头和信息头的数据结构,其中涵盖了诸如文件类型、大小、位图宽度和高度等必要信息。同时,定义了RGB像素类,用于存储每个像素的红、绿、蓝颜色值。 3. **[BMPImage构造函数与像素数据处理]**: `BMPImage`类的构造函数根据传入的宽度和高度创建一个BMP文件,分配像素数据,并初始化必要的头部信息。此外,还实现了一个加载数据的操作,即将像素数据写入文件头部和图像数据部分。 4. **[BMPImage的生成与显示]**: `generate()`方法负责将构建好的BMP位图写入文件中,而`flipVertically()`则实现了垂直翻转图像的功能,这些步骤共同构成了完整的BMP图像生成功能。 5. **[测试程序与测试结果]**: 测试程序通过调用前面实现的BMPImage类和相关类的方法来生成一个测试用的BMP位图。结果显示,我们成功地绘制出了在指定位置有一个白色像素点的图像,证明了功能的正确性。 总的来说,本文详细阐述了如何在不利用外部库的前提下,从一个简单的BMP位图生成器开始,逐步实现了一个基本的图像处理器,包括图像的加载、保存以及基本的变换操作。

正文

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

输出结果

img

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

与Chapter1 p1 Output Image相似的内容:

Chapter1 p1 Output Image

由于本文章是对TinyRenderer的模仿,所以并不打算引入外部库。 那么我们第一步需要解决的就是图形输出的问题,毕竟,如果连渲染的结果都看不到,那还叫什么Renderer嘛。 由于不引入外部库,所以选择输出的图片格式应该越简单越好,各种位图就成为了我们的首选。 这里我们选择了生态较好的bmp位图

[TinyRenderer] Chapter1 p1 Output Image

由于本文章是对TinyRenderer的模仿,所以并不打算引入外部库。 那么我们第一步需要解决的就是图形输出的问题,毕竟,如果连渲染的结果都看不到,那还叫什么Renderer嘛。 由于不引入外部库,所以选择输出的图片格式应该越简单越好,各种位图就成为了我们的首选。 这里我们选择了生态较好的bmp位图

[转帖]Redis里使用Lua

http://me.52fhy.com/lua-book/chapter11.html 版本:自2.6.0起可用。 时间复杂度:取决于执行的脚本。 使用Lua脚本的好处: 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。 原子操作。redis会将整个脚本作为一个整体执行,中间不会被

Chapter1 p2 vec

在上一小节中,我们完成了对BMPImage类的构建,成功实现了我们这个小小引擎的图像输出功能。 你已经完成了图像输出了,接着就开始路径追踪吧。。。 开个玩笑XD 对于曾经学习过一些图形学经典教材的人来说,下一步应当开始着手于画线算法了,但对于本文来说,肯定是要走一些不走寻常路的。 所谓万事开头难,我

[TinyRenderer] Chapter1 p3 Line

(注:本小节不是对划线算法事无巨细的证明,如果你需要更加系统的学习,请跳转至文末的参考部分) 如果你是一名曾经学习过图形学基础的学生,那么你一定对画线算法稔熟于心,中点划线算法,Bresenham算法。其中,现代光栅化器中使用最多的就是Bresenham算法,它以去除了除法和浮点运算而著称。 但如果

程序的机器级表示(CSAPP Chapter 3,COD Chapter 2)

程序的机器级表示(CSAPP Chapter 3,COD Chapter 2) 0. 序言 我们首先回顾计算机执行机器代码的过程和目的。其目的在于处理数据、管理内存、读写数据、通信......。其过程大概可以这样描述:编译器以汇编代码的形式输出,它是机器代码的文本表示,给出程序中的每一条指令。然后

[转帖]Ngx_lua

http://me.52fhy.com/lua-book/chapter12.html 简介 ngx_lua 指的是 lua-nginx-module模块:通过将 LuaJIT 的虚拟机嵌入到 Nginx 的 worker 中,这样既保持高性能,又能不失去lua开发的简单特性。 OpenResty 

[转帖]lua-book

http://me.52fhy.com/lua-book/chapter2.html 数据类型 [TOC] @date: 2018-3-18 Lua中有8个基本类型分别为:nil、boolean、number、string、table、function、userdata、thread。 函数 typ

[转帖]lua-book-运算符

http://me.52fhy.com/lua-book/chapter3.html Lua支持下列主要的运算符: 算术运算符 关系运算符 逻辑运算符 赋值运算符 还支持..、#特殊运算符。其中赋值运算符仅支持=,不支持C语言的+=、++等运算符。 算术运算符 + 加法 - 减法或者负号 * 乘法

[转帖]lua-book-控制语句

http://me.52fhy.com/lua-book/chapter4.html Lua 语言提供的控制结构有 if-else,while,repeat,for,并提供 break、return 关键字来满足更丰富的需求。不支持switch、continue。 Lua 提供的控制语句部分特征类似