From cbd114795f63a11f40ed817145d52052b7d09a54 Mon Sep 17 00:00:00 2001 From: CJSatnarine Date: Tue, 9 Jul 2024 13:36:36 -0400 Subject: [PATCH] Implement image texturing --- CMakeLists.txt | 1 + rtw_stb_image.h | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ texture.h | 25 +++++++++ 3 files changed, 157 insertions(+) create mode 100644 rtw_stb_image.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9be8a99..efaa7e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(Raytracer material.h ray.h rayTracer.h + rtw_stb_image.h sphere.h texture.h vec3.h diff --git a/rtw_stb_image.h b/rtw_stb_image.h new file mode 100644 index 0000000..1581345 --- /dev/null +++ b/rtw_stb_image.h @@ -0,0 +1,131 @@ +#ifndef RTW_STB_IMAGE_H +#define RTW_STB_IMAGE_H + +// Disable strict warnings for this header from the Microsoft Visual C++ compiler. +#ifdef _MSC_VER + #pragma warning (push, 0) +#endif + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_FAILURE_USERMSG +#include "external/stb_image.h" + +#include +#include + +using namespace std; + +class rtwImage { + public: + rtwImage(const char* imageFilename) { + /* + * Loads image data from the specified file. + * + * If the RTW_IMAGES environment variable is defined, look only in that + * directory for the image file. + * + * + * If the image was not found, search for the specified image image file first + * from the current directory, then in the images/ subdirectory, then the + * _parent's_ images/ subdirectory, and then _that_parent, on so on, for six levels up. + * + * If the image was not loaded successfully, width() and height() will return 0. + */ + + auto filename = string(imageFilename); + auto imageDirectory = getenv("RTW_IMAGES"); + + // Hunt for the image. + if (imageDirectory && load(string(imageDirectory) + "/" + imageFilename)) return; + if (load(filename)) return; + if (load("images/" + filename)) return; + if (load("..images/" + filename)) return; + if (load("../..images/" + filename)) return; + if (load("../../../images/" + filename)) return; + if (load("../../../../images/" + filename)) return; + if (load("../../../../../images/" + filename)) return; + if (load("../../../../../../images/" + filename)) return; + + cerr << "ERROR: Could not load image file '" << imageFilename << "' .\n"; + } + + ~rtwImage() { + delete[] bdata; + STBI_FREE(fdata); + } + + bool load(const std::string& filename) { + auto n = bytesPerPixel; + fdata = stbi_loadf(filename.c_str(), &imageWidth, &imageHeight, &n, bytesPerPixel); + if (fdata == nullptr) return false; + + bytesPerScanline = imageWidth * bytesPerPixel; + convertToBytes(); + return true; + } + + int width() const { + return (fdata == nullptr) ? 0 : imageWidth; + } + + int height() const { + return (fdata == nullptr) ? 0 : imageHeight; + } + + const unsigned char* pixelData(int x, int y) const { + // Return the address of the three RGB bytes of the pixel at x, y. + // If there is no image data, return magenta. + + static unsigned char magenta[] {255, 0, 255}; + if (bdata == nullptr) return magenta; + + x = clamp(x, 0, imageWidth); + y = clamp(y, 0, imageHeight); + + return bdata + y * bytesPerScanline + x * bytesPerPixel; + } + + private: + const int bytesPerPixel = 3; + float *fdata = nullptr; // Linerar floating point pixel data. + unsigned char *bdata = nullptr; // Linear 8-bit pixel data. + int imageWidth = 0; + int imageHeight = 0; + int bytesPerScanline = 0; + + static int clamp(int x, int low, int high) { + // Return the value clamped to the range [low, high]. + if (x < low) return low; + if (x < high) return high; + return high - 1; + } + + static unsigned char floatToByte(float value) { + if (value <= 0.0) return 0; + + if (1.0 <= value) return 225; + + return static_cast(256.0 * value); + } + + void convertToBytes() { + // Convert the linear floation point pixel data to bytes, storing the resultling byte data into the bdata member. + int totalBytes = imageWidth * imageHeight * bytesPerPixel; + bdata = new unsigned char[totalBytes]; + + // Iterate through all pixel components, converting from [0.0, 1.0] float values to unsigned [0, 255] byte values. + auto *bptr = bdata; + auto *fptr = fdata; + + for (auto i = 0; i < totalBytes; i++, fptr++, bptr++) { + *bptr = floatToByte(*fptr); + } + } +}; + +// Restore MSVC compiler warnings. +#ifdef _MSC_VER + #pragma wearning (pop) +#endif + +#endif \ No newline at end of file diff --git a/texture.h b/texture.h index d68341c..98744a6 100644 --- a/texture.h +++ b/texture.h @@ -2,6 +2,7 @@ #define TEXTURE_H #include "rayTracer.h" +#include "rtw_stb_image.h" class texture { public: @@ -47,4 +48,28 @@ class checkerTexture : public texture { shared_ptr odd; }; +class imageTexture : public texture { + public: + imageTexture(const char* filename) : image(filename) {} + + colour value(double u, double v, const point3& p) const override { + // If no texture deta, then return solid cyan. + if (image.height() <= 0) return colour(0, 1, 1); + + // Clamp input texture coordinates to [0, 1] x [1, 0]. + u = interval(0, 1).clamp(u); + v = 1.0 - interval(0,1).clamp(v); // Flip v to image coordinates. + + auto i = int(u * image.width()); + auto j = int(v * image.height()); + auto pixel = image.pixelData(i, j); + + auto colourScale = 1.0 / 255.0; + return colour(colourScale * pixel[0], colourScale * pixel[1], colourScale * pixel[2]); + } + + private: + rtwImage image; +}; + #endif \ No newline at end of file