From a41fbf75aff54f4d3bd88793cdf2bf281ea9ee01 Mon Sep 17 00:00:00 2001 From: Keuin Date: Thu, 14 Apr 2022 12:54:06 +0800 Subject: Add pixel and bitmap color depth conversion. --- CMakeLists.txt | 2 +- bitmap.h | 66 ++++++++++++++++++++++++++++++++++++++++++++++--- test.cpp | 77 --------------------------------------------------------- test_bitmap.cpp | 33 +++++++++++++++++++++++++ test_vec.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 82 deletions(-) delete mode 100644 test.cpp create mode 100644 test_bitmap.cpp create mode 100644 test_vec.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c531ea9..91b234c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ add_executable(simple_scanner main_simple_scanner.cpp vec.h bitmap.h ray.h timer # googletest -add_executable(all_tests test.cpp vec.h bitmap.h ray.h) +add_executable(all_tests test_vec.cpp test_bitmap.cpp vec.h bitmap.h ray.h) target_link_libraries(all_tests gtest_main) include(FetchContent) diff --git a/bitmap.h b/bitmap.h index 5b07914..b35bf81 100644 --- a/bitmap.h +++ b/bitmap.h @@ -12,6 +12,7 @@ #include #include "bitfont.h" +#include "vec.h" #define COLORMIX_OVERFLOW_CHECK @@ -29,6 +30,11 @@ template struct pixel { T r, g, b; + static constexpr auto mod = (1ULL << (sizeof(T) * 8U)) - 1ULL; // FIXME + + bool operator==(const pixel &other) const { + return r == other.r && g == other.g && b == other.b; + } /** * Create a pixel of given depth, from normalized color values. @@ -36,7 +42,6 @@ struct pixel { * For example: Set color depth to 8bit, for normalized color (1, 0.5, 0.25), we get: (255, 127, 63). */ static inline pixel from_normalized(double r, double g, double b) { - const auto mod = (1ULL << (sizeof(T) * 8U)) - 1ULL; return pixel{.r = (T) (mod * r), .g = (T) (mod * g), .b = (T) (mod * b)}; } @@ -45,9 +50,27 @@ struct pixel { return from_normalized(v3d.x / 2.0 + 0.5, v3d.y / 2.0 + 0.5, v3d.z / 2.0 + 0.5); } + // Convert pixels between different color depths. + template + static inline pixel from(const pixel &orig) { + return from_normalized( + 1.0 * orig.r / pixel::max_value(), + 1.0 * orig.g / pixel::max_value(), + 1.0 * orig.b / pixel::max_value() + ); + } + static inline pixel black() { return pixel{(T) 0, (T) 0, (T) 0}; } + + static inline pixel white() { + return pixel{(T) mod, (T) mod, (T) mod}; // FIXME float-point values + } + + static inline T max_value() { + return mod; // FIXME + } }; template< @@ -106,9 +129,11 @@ class bitmap { public: bitmap() = delete; - bitmap(unsigned int width, unsigned int height) : width(width), height(height) { - content.resize(width * height, pixel{}); - } + bitmap(unsigned int width, unsigned int height) : + width(width), height(height), content{width * height, pixel{}} {} + + bitmap(unsigned int width, unsigned int height, std::vector> &&data) : + width(width), height(height), content{data} {} static bitmap average(const std::vector> &images) { assert(!images.empty()); @@ -138,12 +163,45 @@ public: return image(x, y); } + const pixel &operator[](size_t loc) const { + assert(loc < width * height); + return content[loc]; + } + + pixel &operator[](size_t loc) { + assert(loc < width * height); + return content[loc]; + } + // Do not use float-point pixels, or this routine will break. void write_plain_ppm(std::ostream &out) const; // Draw text on the image. Supports limited visual ASCII characters. void print(const std::string &s, const pixel &color, unsigned x, unsigned y, unsigned scale = 1, double alpha = 1.0); + + bool normalized() const { + return false; + // TODO return true for float-point pixels + } + + std::pair shape() const { + return std::pair{width, height}; + } + + // U: original color depth, T: desired color depth + template + static bitmap from(const bitmap &src) { + const auto shape = src.shape(); + const size_t sz = shape.first * shape.second; + bitmap out{shape.first, shape.second}; + for (size_t i = 0; i < sz; ++i) { + out[i] = pixel::from(src[i]); + std::cerr << (int) out[i].r << ' ' << (int) out[i].g << ' ' << (int) out[i].b << std::endl; + } + return out; + } + }; template diff --git a/test.cpp b/test.cpp deleted file mode 100644 index 1b765aa..0000000 --- a/test.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// -// Created by Keuin on 2022/4/11. -// - -#include - -#include "vec.h" - -TEST(Vec, VecAdd) { - vec3i a{1, 1, 1}, b{2, 2, 2}, c{3, 3, 3}; - ASSERT_EQ(a + b, c); - - vec3d d{1.1, 2.2, 3.3}, e{4.4, 5.5, 6.6}, f{5.5, 7.7, 9.9}; - ASSERT_EQ(d + e, f); -} - -TEST(Vec, VecMin) { - vec3i a{1, 1, 1}, b{2, 2, 2}, c{-1, -1, -1}; - ASSERT_EQ(a - b, c); - ASSERT_EQ(-a, c); - - vec3d d{1.1, 2.2, 3.3}, e{-4.4, -5.5, -6.6}, f{5.5, 7.7, 9.9}; - ASSERT_EQ(d - e, f); - ASSERT_EQ(d + (-e), f); -} - -TEST(Vec, CrossProduct) { - vec3i a{1, 1, 1}, b{2, 2, 2}, c{3, 4, 5}, d{6, 7, 8}, e{-3, -6, -3}; - ASSERT_EQ(cross(a, b), vec3i{}); - ASSERT_EQ(a.cross(b), vec3i{}); - ASSERT_EQ(cross(c, d), e); - ASSERT_EQ(c.cross(d), e); - - vec3d f{3, 4, 5}, g{6, 7, 8}, h{-3, -6, -3}; - ASSERT_EQ(cross(f, g), h); - ASSERT_EQ(f.cross(g), h); -} - -TEST(Vec, DotProduct) { - vec3i c{3, 4, 5}, d{6, 7, 8}; - int e = 18 + 28 + 40; - ASSERT_EQ(dot(c, d), e); - ASSERT_EQ(c.dot(d), e); - - vec3d f{3, 4, 5}, g{6, 7, 8}; - ASSERT_EQ(dot(f, g), e); - ASSERT_EQ(f.dot(g), e); -} - -TEST(Vec, MulByScalar) { - vec3i a{1, 1, 1}, b{2, 2, 2}; - vec3d c{7, 14, 21}, d{1, 2, 3}; - ASSERT_EQ(a * 2, b); - ASSERT_EQ(a / 0.5, b); - ASSERT_EQ(2 * a, b); - ASSERT_EQ(c * (1.0 / 7), d); - ASSERT_EQ(c / 7, d); - ASSERT_EQ(c / 7.0, d); -} - -TEST(Vec, Norm) { - vec3d a{1, 1, 1}, b{2, 2, 2}; - ASSERT_EQ(a.norm(), sqrt(3)); - ASSERT_EQ(b.norm(), sqrt(12)); -} - -TEST(Vec, UnitVec) { - vec3d a{1, 2, 2}, b{1.0 / 3, 2.0 / 3, 2.0 / 3}; - ASSERT_EQ(a.unit_vec(), b); -} - -TEST(Vec, Mod2) { - vec3i a{1, 2, 3}; - vec3d b{2.5, 3, 1.2}; - ASSERT_EQ(a.mod2(), 14); - ASSERT_LE(abs(b.mod2() - (2.5 * 2.5 + 3 * 3 + 1.2 * 1.2)), 1e-10); -} \ No newline at end of file diff --git a/test_bitmap.cpp b/test_bitmap.cpp new file mode 100644 index 0000000..4f20d3f --- /dev/null +++ b/test_bitmap.cpp @@ -0,0 +1,33 @@ +// +// Created by Keuin on 2022/4/14. +// + +#include + +#include "bitmap.h" + +TEST(Pixel, PixelDepthConvert) { + const auto white8 = pixel8b::white(); + const auto white32 = pixel::white(); + ASSERT_EQ(white8, pixel8b::from(white32)); + + const auto black8 = pixel8b::black(); + const auto black32 = pixel::black(); + ASSERT_EQ(black8, pixel8b::from(black32)); + + const auto gray8 = pixel8b{127, 127, 127}; + const auto gray32 = pixel{ + ((1ULL << 32U) - 1U) >> 1U, + ((1ULL << 32U) - 1U) >> 1U, + ((1ULL << 32U) - 1U) >> 1U + }; + ASSERT_EQ(gray8, pixel8b::from(gray32)); + + const auto mix8 = pixel8b{0, 127, 255}; + const auto mix32 = pixel{ + 0, + ((1ULL << 32U) - 1U) >> 1U, + ((1ULL << 32U) - 1U) + }; + ASSERT_EQ(mix8, pixel8b::from(mix32)); +} \ No newline at end of file diff --git a/test_vec.cpp b/test_vec.cpp new file mode 100644 index 0000000..1b765aa --- /dev/null +++ b/test_vec.cpp @@ -0,0 +1,77 @@ +// +// Created by Keuin on 2022/4/11. +// + +#include + +#include "vec.h" + +TEST(Vec, VecAdd) { + vec3i a{1, 1, 1}, b{2, 2, 2}, c{3, 3, 3}; + ASSERT_EQ(a + b, c); + + vec3d d{1.1, 2.2, 3.3}, e{4.4, 5.5, 6.6}, f{5.5, 7.7, 9.9}; + ASSERT_EQ(d + e, f); +} + +TEST(Vec, VecMin) { + vec3i a{1, 1, 1}, b{2, 2, 2}, c{-1, -1, -1}; + ASSERT_EQ(a - b, c); + ASSERT_EQ(-a, c); + + vec3d d{1.1, 2.2, 3.3}, e{-4.4, -5.5, -6.6}, f{5.5, 7.7, 9.9}; + ASSERT_EQ(d - e, f); + ASSERT_EQ(d + (-e), f); +} + +TEST(Vec, CrossProduct) { + vec3i a{1, 1, 1}, b{2, 2, 2}, c{3, 4, 5}, d{6, 7, 8}, e{-3, -6, -3}; + ASSERT_EQ(cross(a, b), vec3i{}); + ASSERT_EQ(a.cross(b), vec3i{}); + ASSERT_EQ(cross(c, d), e); + ASSERT_EQ(c.cross(d), e); + + vec3d f{3, 4, 5}, g{6, 7, 8}, h{-3, -6, -3}; + ASSERT_EQ(cross(f, g), h); + ASSERT_EQ(f.cross(g), h); +} + +TEST(Vec, DotProduct) { + vec3i c{3, 4, 5}, d{6, 7, 8}; + int e = 18 + 28 + 40; + ASSERT_EQ(dot(c, d), e); + ASSERT_EQ(c.dot(d), e); + + vec3d f{3, 4, 5}, g{6, 7, 8}; + ASSERT_EQ(dot(f, g), e); + ASSERT_EQ(f.dot(g), e); +} + +TEST(Vec, MulByScalar) { + vec3i a{1, 1, 1}, b{2, 2, 2}; + vec3d c{7, 14, 21}, d{1, 2, 3}; + ASSERT_EQ(a * 2, b); + ASSERT_EQ(a / 0.5, b); + ASSERT_EQ(2 * a, b); + ASSERT_EQ(c * (1.0 / 7), d); + ASSERT_EQ(c / 7, d); + ASSERT_EQ(c / 7.0, d); +} + +TEST(Vec, Norm) { + vec3d a{1, 1, 1}, b{2, 2, 2}; + ASSERT_EQ(a.norm(), sqrt(3)); + ASSERT_EQ(b.norm(), sqrt(12)); +} + +TEST(Vec, UnitVec) { + vec3d a{1, 2, 2}, b{1.0 / 3, 2.0 / 3, 2.0 / 3}; + ASSERT_EQ(a.unit_vec(), b); +} + +TEST(Vec, Mod2) { + vec3i a{1, 2, 3}; + vec3d b{2.5, 3, 1.2}; + ASSERT_EQ(a.mod2(), 14); + ASSERT_LE(abs(b.mod2() - (2.5 * 2.5 + 3 * 3 + 1.2 * 1.2)), 1e-10); +} \ No newline at end of file -- cgit v1.2.3