diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d8f956c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +# CMakeList.txt: 顶层 CMake 项目文件,在此处执行全局配置 +# 并包含子项目。 +# +cmake_minimum_required (VERSION 3.8) + +# 如果支持,请为 MSVC 编译器启用热重载。 +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + +project ("tinyrender") + +# 包含子项目。 +add_subdirectory ("tinyrender") diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..f4bc98b --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,101 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux-debug", + "displayName": "Linux Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "macos-debug", + "displayName": "macOS Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + } + ] +} diff --git a/tinyrender/CMakeLists.txt b/tinyrender/CMakeLists.txt new file mode 100644 index 0000000..1916678 --- /dev/null +++ b/tinyrender/CMakeLists.txt @@ -0,0 +1,12 @@ +# CMakeList.txt: tinyrender 的 CMake 项目,在此处包括源代码并定义 +# 项目特定的逻辑。 +# + +# 将源代码添加到此项目的可执行文件。 +add_executable (tinyrender "main.cpp" "tgaimage.h" "tgaimage.cpp") + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET tinyrender PROPERTY CXX_STANDARD 20) +endif() + +# TODO: 如有需要,请添加测试并安装目标。 diff --git a/tinyrender/main.cpp b/tinyrender/main.cpp new file mode 100644 index 0000000..7b0819c --- /dev/null +++ b/tinyrender/main.cpp @@ -0,0 +1,13 @@ +#include "tgaimage.h" + +const TGAColor white = TGAColor(255, 255, 255, 255); +const TGAColor red = TGAColor(255, 0, 0, 255); + +int main(int argc, char** argv) { + TGAImage image(100, 100, TGAImage::RGB); + image.set(52, 41, red); + image.flip_vertically(); // i want to have the origin at the left bottom corner of the image + image.write_tga_file("output.tga"); + return 0; +} + diff --git a/tinyrender/tgaimage.cpp b/tinyrender/tgaimage.cpp new file mode 100644 index 0000000..721baff --- /dev/null +++ b/tinyrender/tgaimage.cpp @@ -0,0 +1,355 @@ +#include +#include +#include +#include +#include +#include "tgaimage.h" + +TGAImage::TGAImage() : data(NULL), width(0), height(0), bytespp(0) { +} + +TGAImage::TGAImage(int w, int h, int bpp) : data(NULL), width(w), height(h), bytespp(bpp) { + unsigned long nbytes = width * height * bytespp; + data = new unsigned char[nbytes]; + memset(data, 0, nbytes); +} + +TGAImage::TGAImage(const TGAImage& img) { + width = img.width; + height = img.height; + bytespp = img.bytespp; + unsigned long nbytes = width * height * bytespp; + data = new unsigned char[nbytes]; + memcpy(data, img.data, nbytes); +} + +TGAImage::~TGAImage() { + if (data) delete[] data; +} + +TGAImage& TGAImage::operator =(const TGAImage& img) { + if (this != &img) { + if (data) delete[] data; + width = img.width; + height = img.height; + bytespp = img.bytespp; + unsigned long nbytes = width * height * bytespp; + data = new unsigned char[nbytes]; + memcpy(data, img.data, nbytes); + } + return *this; +} + +bool TGAImage::read_tga_file(const char* filename) { + if (data) delete[] data; + data = NULL; + std::ifstream in; + in.open(filename, std::ios::binary); + if (!in.is_open()) { + std::cerr << "can't open file " << filename << "\n"; + in.close(); + return false; + } + TGA_Header header; + in.read((char*)&header, sizeof(header)); + if (!in.good()) { + in.close(); + std::cerr << "an error occured while reading the header\n"; + return false; + } + width = header.width; + height = header.height; + bytespp = header.bitsperpixel >> 3; + if (width <= 0 || height <= 0 || (bytespp != GRAYSCALE && bytespp != RGB && bytespp != RGBA)) { + in.close(); + std::cerr << "bad bpp (or width/height) value\n"; + return false; + } + unsigned long nbytes = bytespp * width * height; + data = new unsigned char[nbytes]; + if (3 == header.datatypecode || 2 == header.datatypecode) { + in.read((char*)data, nbytes); + if (!in.good()) { + in.close(); + std::cerr << "an error occured while reading the data\n"; + return false; + } + } + else if (10 == header.datatypecode || 11 == header.datatypecode) { + if (!load_rle_data(in)) { + in.close(); + std::cerr << "an error occured while reading the data\n"; + return false; + } + } + else { + in.close(); + std::cerr << "unknown file format " << (int)header.datatypecode << "\n"; + return false; + } + if (!(header.imagedescriptor & 0x20)) { + flip_vertically(); + } + if (header.imagedescriptor & 0x10) { + flip_horizontally(); + } + std::cerr << width << "x" << height << "/" << bytespp * 8 << "\n"; + in.close(); + return true; +} + +bool TGAImage::load_rle_data(std::ifstream& in) { + unsigned long pixelcount = width * height; + unsigned long currentpixel = 0; + unsigned long currentbyte = 0; + TGAColor colorbuffer; + do { + unsigned char chunkheader = 0; + chunkheader = in.get(); + if (!in.good()) { + std::cerr << "an error occured while reading the data\n"; + return false; + } + if (chunkheader < 128) { + chunkheader++; + for (int i = 0; i < chunkheader; i++) { + in.read((char*)colorbuffer.raw, bytespp); + if (!in.good()) { + std::cerr << "an error occured while reading the header\n"; + return false; + } + for (int t = 0; t < bytespp; t++) + data[currentbyte++] = colorbuffer.raw[t]; + currentpixel++; + if (currentpixel > pixelcount) { + std::cerr << "Too many pixels read\n"; + return false; + } + } + } + else { + chunkheader -= 127; + in.read((char*)colorbuffer.raw, bytespp); + if (!in.good()) { + std::cerr << "an error occured while reading the header\n"; + return false; + } + for (int i = 0; i < chunkheader; i++) { + for (int t = 0; t < bytespp; t++) + data[currentbyte++] = colorbuffer.raw[t]; + currentpixel++; + if (currentpixel > pixelcount) { + std::cerr << "Too many pixels read\n"; + return false; + } + } + } + } while (currentpixel < pixelcount); + return true; +} + +bool TGAImage::write_tga_file(const char* filename, bool rle) { + unsigned char developer_area_ref[4] = { 0, 0, 0, 0 }; + unsigned char extension_area_ref[4] = { 0, 0, 0, 0 }; + unsigned char footer[18] = { 'T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0' }; + std::ofstream out; + out.open(filename, std::ios::binary); + if (!out.is_open()) { + std::cerr << "can't open file " << filename << "\n"; + out.close(); + return false; + } + TGA_Header header; + memset((void*)&header, 0, sizeof(header)); + header.bitsperpixel = bytespp << 3; + header.width = width; + header.height = height; + header.datatypecode = (bytespp == GRAYSCALE ? (rle ? 11 : 3) : (rle ? 10 : 2)); + header.imagedescriptor = 0x20; // top-left origin + out.write((char*)&header, sizeof(header)); + if (!out.good()) { + out.close(); + std::cerr << "can't dump the tga file\n"; + return false; + } + if (!rle) { + out.write((char*)data, width * height * bytespp); + if (!out.good()) { + std::cerr << "can't unload raw data\n"; + out.close(); + return false; + } + } + else { + if (!unload_rle_data(out)) { + out.close(); + std::cerr << "can't unload rle data\n"; + return false; + } + } + out.write((char*)developer_area_ref, sizeof(developer_area_ref)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + out.close(); + return false; + } + out.write((char*)extension_area_ref, sizeof(extension_area_ref)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + out.close(); + return false; + } + out.write((char*)footer, sizeof(footer)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + out.close(); + return false; + } + out.close(); + return true; +} + +// TODO: it is not necessary to break a raw chunk for two equal pixels (for the matter of the resulting size) +bool TGAImage::unload_rle_data(std::ofstream& out) { + const unsigned char max_chunk_length = 128; + unsigned long npixels = width * height; + unsigned long curpix = 0; + while (curpix < npixels) { + unsigned long chunkstart = curpix * bytespp; + unsigned long curbyte = curpix * bytespp; + unsigned char run_length = 1; + bool raw = true; + while (curpix + run_length < npixels && run_length < max_chunk_length) { + bool succ_eq = true; + for (int t = 0; succ_eq && t < bytespp; t++) { + succ_eq = (data[curbyte + t] == data[curbyte + t + bytespp]); + } + curbyte += bytespp; + if (1 == run_length) { + raw = !succ_eq; + } + if (raw && succ_eq) { + run_length--; + break; + } + if (!raw && !succ_eq) { + break; + } + run_length++; + } + curpix += run_length; + out.put(raw ? run_length - 1 : run_length + 127); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + return false; + } + out.write((char*)(data + chunkstart), (raw ? run_length * bytespp : bytespp)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + return false; + } + } + return true; +} + +TGAColor TGAImage::get(int x, int y) { + if (!data || x < 0 || y < 0 || x >= width || y >= height) { + return TGAColor(); + } + return TGAColor(data + (x + y * width) * bytespp, bytespp); +} + +bool TGAImage::set(int x, int y, TGAColor c) { + if (!data || x < 0 || y < 0 || x >= width || y >= height) { + return false; + } + memcpy(data + (x + y * width) * bytespp, c.raw, bytespp); + return true; +} + +int TGAImage::get_bytespp() { + return bytespp; +} + +int TGAImage::get_width() { + return width; +} + +int TGAImage::get_height() { + return height; +} + +bool TGAImage::flip_horizontally() { + if (!data) return false; + int half = width >> 1; + for (int i = 0; i < half; i++) { + for (int j = 0; j < height; j++) { + TGAColor c1 = get(i, j); + TGAColor c2 = get(width - 1 - i, j); + set(i, j, c2); + set(width - 1 - i, j, c1); + } + } + return true; +} + +bool TGAImage::flip_vertically() { + if (!data) return false; + unsigned long bytes_per_line = width * bytespp; + unsigned char* line = new unsigned char[bytes_per_line]; + int half = height >> 1; + for (int j = 0; j < half; j++) { + unsigned long l1 = j * bytes_per_line; + unsigned long l2 = (height - 1 - j) * bytes_per_line; + memmove((void*)line, (void*)(data + l1), bytes_per_line); + memmove((void*)(data + l1), (void*)(data + l2), bytes_per_line); + memmove((void*)(data + l2), (void*)line, bytes_per_line); + } + delete[] line; + return true; +} + +unsigned char* TGAImage::buffer() { + return data; +} + +void TGAImage::clear() { + memset((void*)data, 0, width * height * bytespp); +} + +bool TGAImage::scale(int w, int h) { + if (w <= 0 || h <= 0 || !data) return false; + unsigned char* tdata = new unsigned char[w * h * bytespp]; + int nscanline = 0; + int oscanline = 0; + int erry = 0; + unsigned long nlinebytes = w * bytespp; + unsigned long olinebytes = width * bytespp; + for (int j = 0; j < height; j++) { + int errx = width - w; + int nx = -bytespp; + int ox = -bytespp; + for (int i = 0; i < width; i++) { + ox += bytespp; + errx += w; + while (errx >= (int)width) { + errx -= width; + nx += bytespp; + memcpy(tdata + nscanline + nx, data + oscanline + ox, bytespp); + } + } + erry += h; + oscanline += olinebytes; + while (erry >= (int)height) { + if (erry >= (int)height << 1) // it means we jump over a scanline + memcpy(tdata + nscanline + nlinebytes, tdata + nscanline, nlinebytes); + erry -= height; + nscanline += nlinebytes; + } + } + delete[] data; + data = tdata; + width = w; + height = h; + return true; +} \ No newline at end of file diff --git a/tinyrender/tgaimage.h b/tinyrender/tgaimage.h new file mode 100644 index 0000000..8e6d7c0 --- /dev/null +++ b/tinyrender/tgaimage.h @@ -0,0 +1,96 @@ +#ifndef __IMAGE_H__ +#define __IMAGE_H__ + +#include + +#pragma pack(push,1) +struct TGA_Header { + char idlength; + char colormaptype; + char datatypecode; + short colormaporigin; + short colormaplength; + char colormapdepth; + short x_origin; + short y_origin; + short width; + short height; + char bitsperpixel; + char imagedescriptor; +}; +#pragma pack(pop) + + + +struct TGAColor { + union { + struct { + unsigned char b, g, r, a; + }; + unsigned char raw[4]; + unsigned int val; + }; + int bytespp; + + TGAColor() : val(0), bytespp(1) { + } + + TGAColor(unsigned char R, unsigned char G, unsigned char B, unsigned char A) : b(B), g(G), r(R), a(A), bytespp(4) { + } + + TGAColor(int v, int bpp) : val(v), bytespp(bpp) { + } + + TGAColor(const TGAColor& c) : val(c.val), bytespp(c.bytespp) { + } + + TGAColor(const unsigned char* p, int bpp) : val(0), bytespp(bpp) { + for (int i = 0; i < bpp; i++) { + raw[i] = p[i]; + } + } + + TGAColor& operator =(const TGAColor& c) { + if (this != &c) { + bytespp = c.bytespp; + val = c.val; + } + return *this; + } +}; + + +class TGAImage { +protected: + unsigned char* data; + int width; + int height; + int bytespp; + + bool load_rle_data(std::ifstream& in); + bool unload_rle_data(std::ofstream& out); +public: + enum Format { + GRAYSCALE = 1, RGB = 3, RGBA = 4 + }; + + TGAImage(); + TGAImage(int w, int h, int bpp); + TGAImage(const TGAImage& img); + bool read_tga_file(const char* filename); + bool write_tga_file(const char* filename, bool rle = true); + bool flip_horizontally(); + bool flip_vertically(); + bool scale(int w, int h); + TGAColor get(int x, int y); + bool set(int x, int y, TGAColor c); + ~TGAImage(); + TGAImage& operator =(const TGAImage& img); + int get_width(); + int get_height(); + int get_bytespp(); + unsigned char* buffer(); + void clear(); +}; + +#endif //__IMAGE_H__