diff --git a/library/phx/resources/types/lookup_table.cpp b/library/phx/resources/types/lookup_table.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7cf2b13b0995460147a509f00bbe85dd81d0f7be --- /dev/null +++ b/library/phx/resources/types/lookup_table.cpp @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "phx/resources/types/lookup_table.hpp" + +#include <memory> + +namespace phx { +LookupTable::LookupTable(const TransferFunction& transfer_function, + int num_colors) + : min_key_(transfer_function.GetEntries().front().scalar), + max_key_(transfer_function.GetEntries().back().scalar), + transfer_function_(transfer_function) { + Generate(num_colors); +} + +void LookupTable::Generate(int num_colors) { + float step = (max_key_ - min_key_) / (num_colors - 1); + for (auto i = 0; i < num_colors; ++i) { + lookup_table_.push_back( + transfer_function_.Interpolate(min_key_ + i * step)); + } +} +glm::vec4 LookupTable::GetColor(std::size_t index) const { + return lookup_table_.at(index); +} + +std::size_t LookupTable::GetIndex(float key) const { + return static_cast<std::size_t>( + std::round((GetSize() - 1) * ((key - min_key_) / (max_key_ - min_key_)))); +} + +std::size_t LookupTable::GetSize() const { return lookup_table_.size(); } + +std::unique_ptr<gl::texture_1d> LookupTable::GetTexture1D() const { + auto lookup_texture = std::make_unique<gl::texture_1d>(); + lookup_texture->set_storage(0, GL_RGBA, + static_cast<GLsizei>(lookup_table_.size())); + lookup_texture->set_sub_image(0, 0, + static_cast<GLsizei>(lookup_table_.size()), + GL_RGBA8, GL_FLOAT, lookup_table_.data()); + return lookup_texture; +} + +} // namespace phx diff --git a/library/phx/resources/types/lookup_table.hpp b/library/phx/resources/types/lookup_table.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ef9a920a4d80e7d795beb6aa65a2ee22fea5dd3d --- /dev/null +++ b/library/phx/resources/types/lookup_table.hpp @@ -0,0 +1,65 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef LIBRARY_PHX_RESOURCES_TYPES_LOOKUP_TABLE_HPP_ +#define LIBRARY_PHX_RESOURCES_TYPES_LOOKUP_TABLE_HPP_ + +#include <memory> +#include <vector> + +#include "phx/resources/types/shader_source.hpp" +#include "phx/resources/types/transfer_function.hpp" +#include "phx/suppress_warnings.hpp" + +SUPPRESS_WARNINGS_BEGIN +#include "gl/texture.hpp" +#include "glm/detail/type_vec4.hpp" +SUPPRESS_WARNINGS_END + +namespace phx { + +class PHOENIX_EXPORT LookupTable { + public: + LookupTable(const TransferFunction& transfer_function, int num_colors); + LookupTable(const LookupTable&) = default; + LookupTable(LookupTable&&) = default; + ~LookupTable() = default; + LookupTable& operator=(const LookupTable&) = default; + LookupTable& operator=(LookupTable&&) = default; + + std::size_t GetIndex(float key) const; + glm::vec4 GetColor(std::size_t index) const; + std::size_t GetSize() const; + + std::unique_ptr<gl::texture_1d> GetTexture1D() const; + + private: + void Generate(int num_colors); + + float min_key_; + float max_key_; + std::vector<glm::vec4> lookup_table_; + const TransferFunction transfer_function_; +}; +} // namespace phx + +#endif // LIBRARY_PHX_RESOURCES_TYPES_LOOKUP_TABLE_HPP_ diff --git a/library/phx/resources/types/transfer_function.cpp b/library/phx/resources/types/transfer_function.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a29c2304eb477ed859fc29a00a159f20bf04522 --- /dev/null +++ b/library/phx/resources/types/transfer_function.cpp @@ -0,0 +1,142 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "phx/resources/types/transfer_function.hpp" + +#include <algorithm> +#include <fstream> +#include <memory> +#include <string> +#include <vector> + +#include "phx/core/logger.hpp" + +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/gtx/compatibility.hpp" + +#include "json.hpp" + +namespace phx { +TransferFunction::TransferFunction(const InterpolationMode mode) + : mode_(mode) {} + +glm::vec4 TransferFunction::Interpolate(float scalar) const { + if (entries_.empty()) { + phx::warn("TransferFunction is empty! Defaulting to black."); + return glm::vec4(); + } + + if (scalar > entries_.back().scalar) { + phx::warn( + "WARNING: Passed value is out of TransferFunction range! Setting to " + "maximum."); + return entries_.back().color; + } + + if (scalar < entries_.front().scalar) { + phx::warn( + "WARNING: Passed value is out of TransferFunction range! Setting to " + "minimum."); + return entries_.front().color; + } + + const auto iterator = std::find_if( + entries_.begin(), entries_.end(), + [=](const Entry& iteratee) { return iteratee.scalar > scalar; }); + const std::size_t upper_index = + static_cast<std::size_t>(std::distance(entries_.begin(), iterator)); + if (upper_index == 0) { + return entries_[upper_index].color; + } + if (upper_index == entries_.size()) { + return entries_.back().color; + } + const std::size_t lower_index = upper_index - 1; + const auto offset = + scalar - static_cast<float>(entries_[lower_index].scalar / + entries_[upper_index].scalar - + entries_[lower_index].scalar); + + if (mode_ == InterpolationMode::NEAREST_NEIGHBOR) { + return offset >= 0.5f ? entries_[upper_index].color + : entries_[lower_index].color; + } + if (mode_ == InterpolationMode::LINEAR) { + return glm::lerp(entries_[lower_index].color, entries_[upper_index].color, + offset); + } + return glm::vec4(); +} + +void TransferFunction::Load(const std::string& filepath) { + std::ifstream file(filepath); + + nlohmann::json json; + file >> json; + + auto json_array = json["entries"]; + for (auto& json_entry : json_array) { + float json_scalar = json_entry["scalar"]; + std::vector<float> json_colors = json_entry["color"]; + entries_.push_back( + {json_scalar, + {json_colors[0], json_colors[1], json_colors[2], json_colors[3]}}); + std::sort(entries_.begin(), entries_.end(), + [](Entry a, Entry b) { return a.scalar <= b.scalar; }); + } +} + +void TransferFunction::Save(const std::string& filepath) const { + auto json_array = nlohmann::json::array(); + for (auto& entry : entries_) { + nlohmann::json json_entry; + json_entry["scalar"] = entry.scalar; + json_entry["color"] = nlohmann::json::array( + {entry.color.r, entry.color.g, entry.color.b, entry.color.a}); + json_array.push_back(json_entry); + } + + nlohmann::json json; + json["entries"] = json_array; + + std::ofstream file(filepath); + file << json; +} + +const std::vector<TransferFunction::Entry>& TransferFunction::GetEntries() + const { + return entries_; +} + +void TransferFunction::SetEntries(const std::vector<Entry>& entries) { + entries_ = entries; + std::sort(entries_.begin(), entries_.end(), + [](Entry a, Entry b) { return a.scalar <= b.scalar; }); +} + +TransferFunction::InterpolationMode TransferFunction::GetMode() const { + return mode_; +} + +void TransferFunction::SetMode(InterpolationMode mode) { mode_ = mode; } + +} // namespace phx diff --git a/library/phx/resources/types/transfer_function.hpp b/library/phx/resources/types/transfer_function.hpp new file mode 100644 index 0000000000000000000000000000000000000000..79500cfdfaebd6b3ea7cb49912886d464176b4c4 --- /dev/null +++ b/library/phx/resources/types/transfer_function.hpp @@ -0,0 +1,83 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef LIBRARY_PHX_RESOURCES_TYPES_TRANSFER_FUNCTION_HPP_ +#define LIBRARY_PHX_RESOURCES_TYPES_TRANSFER_FUNCTION_HPP_ + +#include <memory> +#include <string> +#include <vector> + +#include "phx/resources/types/shader_source.hpp" +#include "phx/suppress_warnings.hpp" +#include "phx/utility/epsilon_comparison.hpp" + +SUPPRESS_WARNINGS_BEGIN +#include "gl/texture.hpp" +#include "glm/detail/type_vec1.hpp" +#include "glm/detail/type_vec4.hpp" +SUPPRESS_WARNINGS_END + +namespace phx { + +class PHOENIX_EXPORT TransferFunction { + public: + enum class InterpolationMode { NEAREST_NEIGHBOR, LINEAR }; + struct PHOENIX_EXPORT Entry { + float scalar; + glm::vec4 color; + + bool operator==(const Entry& that) const { + return phx::equals<float>(scalar, that.scalar) && color == that.color; + } + bool operator!=(const Entry& that) const { + return !phx::equals<float>(scalar, that.scalar) || color != that.color; + } + }; + + explicit TransferFunction( + InterpolationMode interpolation_mode = InterpolationMode::LINEAR); + TransferFunction(const TransferFunction&) = default; + TransferFunction(TransferFunction&&) = default; + ~TransferFunction() = default; + TransferFunction& operator=(const TransferFunction&) = default; + TransferFunction& operator=(TransferFunction&&) = default; + + glm::vec4 Interpolate(float scalar) const; + + void Load(const std::string& filepath); + void Save(const std::string& filepath) const; + + const std::vector<Entry>& GetEntries() const; + void SetEntries(const std::vector<Entry>& entries); + + InterpolationMode GetMode() const; + void SetMode(InterpolationMode mode); + + private: + std::vector<Entry> entries_; // This is always sorted by scalar increasingly. + InterpolationMode mode_ = InterpolationMode::LINEAR; +}; + +} // namespace phx + +#endif // LIBRARY_PHX_RESOURCES_TYPES_TRANSFER_FUNCTION_HPP_ diff --git a/library/phx/utility/epsilon_comparison.hpp b/library/phx/utility/epsilon_comparison.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a3202823159f7797ddc046498fd8e4850265d1c8 --- /dev/null +++ b/library/phx/utility/epsilon_comparison.hpp @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#ifndef LIBRARY_PHX_UTILITY_EPSILON_COMPARISON_HPP_ +#define LIBRARY_PHX_UTILITY_EPSILON_COMPARISON_HPP_ + +#include <cmath> + +namespace phx { + +template <typename type> +const type epsilon() { + return type(0.0001); +} + +template <typename type> +bool equals(const type& lhs, const type& rhs) { + return std::abs(lhs - rhs) < epsilon<type>(); +} + +} // namespace phx + +#endif // LIBRARY_PHX_UTILITY_EPSILON_COMPARISON_HPP_ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f63ed905d8e76ba508e35cf4c75092d0c56031d9..f8633caf777fe3f118dc1efe0890986fa4bd9df3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -187,6 +187,7 @@ add_mocked_test(test_vr_controller LIBRARIES phoenix test_utili add_mocked_test(test_assimp_loader LIBRARIES phoenix test_utilities MOCKS opengl_mock) add_mocked_test(test_model LIBRARIES phoenix MOCKS opengl_mock) add_mocked_test(test_scene_loader LIBRARIES phoenix MOCKS opengl_mock) +add_mocked_test(test_lookup_table LIBRARIES phoenix MOCKS opengl_mock) add_mocked_test(integration_test_model_rendering LIBRARIES phoenix test_utilities MOCKS openvr_mock) add_mocked_test(integration_test_opengl_buffer_data_download LIBRARIES phoenix test_utilities MOCKS openvr_mock) diff --git a/tests/src/mocks/generation/Create_openGL_mock.py b/tests/src/mocks/generation/Create_openGL_mock.py index 07c1944f1a3092d356c28ca23dee79d73bf1e640..cec127ab9145a30030fd965840ed33bc96956411 100644 --- a/tests/src/mocks/generation/Create_openGL_mock.py +++ b/tests/src/mocks/generation/Create_openGL_mock.py @@ -22,6 +22,7 @@ import sys, getopt #functions you want to mock should be put in this list + functions_to_mock = ['__glewBindAttribLocation', '__glewBindBuffer', '__glewBindBufferBase', '__glewBindBufferRange', '__glewBindFragDataLocation', @@ -142,8 +143,9 @@ functions_to_mock = ['__glewBindAttribLocation', '__glewReleaseShaderCompiler', '__glewShaderBinary', '__glewShaderStorageBlockBinding', '__glewSpecializeShader', '__glewStencilMaskSeparate', - '__glewTextureParameteri', '__glewTextureStorage2D', - '__glewTextureStorage3D', '__glewTextureSubImage2D', + '__glewTextureParameteri', '__glewTextureStorage1D', + '__glewTextureStorage2D', '__glewTextureStorage3D', + '__glewTextureSubImage1D', '__glewTextureSubImage2D', '__glewTextureSubImage3D', '__glewTransformFeedbackVaryings', '__glewUniformBlockBinding', '__glewUniformSubroutinesuiv', diff --git a/tests/src/test_lookup_table.cpp b/tests/src/test_lookup_table.cpp new file mode 100644 index 0000000000000000000000000000000000000000..17d21d9878c998a0229c9d7bd1b614a6fbce39ea --- /dev/null +++ b/tests/src/test_lookup_table.cpp @@ -0,0 +1,76 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include "catch/catch.hpp" + +#include "phx/resources/types/lookup_table.hpp" +#include "phx/resources/types/transfer_function.hpp" + +#include "trompeloeil.hpp" + +#include "mocks/opengl_mock.hpp" + +SCENARIO("Creating and accesing a Lookup Table", "[phx][phx::LookupTable]") { + OPENGL_MOCK_ALLOW_ANY_CALL + GIVEN("A test transfer function") { + phx::TransferFunction transfer_function; + glm::vec4 red_color{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec4 black_color{0.0f, 0.0f, 0.0f, 0.0f}; + transfer_function.SetEntries({{0.0f, red_color}, {1.0f, black_color}}); + + WHEN("We create Lookup Table with the given Transfer function") { + phx::LookupTable lut(transfer_function, 11); + + THEN("The Lookup Table is generated correctly") { + REQUIRE(lut.GetColor(0) == red_color); + REQUIRE(lut.GetColor(2) == glm::vec4(0.8f, 0.0f, 0.0f, 0.0f)); + REQUIRE(lut.GetColor(5) == glm::vec4(0.5f, 0.0f, 0.0f, 0.0f)); + REQUIRE(lut.GetColor(7) == glm::vec4(0.3f, 0.0f, 0.0f, 0.0f)); + REQUIRE(lut.GetColor(10) == black_color); + } + + THEN("Keys can be converted indices") { + REQUIRE(lut.GetIndex(0.0f) == 0); + REQUIRE(lut.GetIndex(1.0f) == 10); + REQUIRE(lut.GetIndex(0.56f) == 6); + } + + THEN("We can ask for a lookup table as texture1D") { + REQUIRE_CALL(open_gl_mock, glCreateTextures(_, _, _)); + auto lookup_texture = lut.GetTexture1D(); + } + } + + WHEN("We ask for a nearest neighbor interpolated Lookup Table") { + transfer_function.SetMode( + phx::TransferFunction::InterpolationMode::NEAREST_NEIGHBOR); + phx::LookupTable lut(transfer_function, 11); + THEN("The Lookuptable is generated correctly") { + REQUIRE(lut.GetColor(0) == red_color); + REQUIRE(lut.GetColor(2) == red_color); + REQUIRE(lut.GetColor(5) == black_color); + REQUIRE(lut.GetColor(7) == black_color); + REQUIRE(lut.GetColor(10) == black_color); + } + } + } +} diff --git a/tests/src/test_transfer_function.cpp b/tests/src/test_transfer_function.cpp new file mode 100644 index 0000000000000000000000000000000000000000..38d2ef046f8fd64154020ac55ef23046189489ac --- /dev/null +++ b/tests/src/test_transfer_function.cpp @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +// Project Phoenix +// +// Copyright (c) 2017-2018 RWTH Aachen University, Germany, +// Virtual Reality & Immersive Visualization Group. +//------------------------------------------------------------------------------ +// License +// +// Licensed under the 3-Clause BSD License (the "License"); +// you may not use this file except in compliance with the License. +// See the file LICENSE for the full text. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//------------------------------------------------------------------------------ + +#include <memory> + +#include "phx/core/logger.hpp" +#include "phx/resources/types/transfer_function.hpp" + +#include "test_utilities/glm_vec.hpp" + +#include "catch/catch.hpp" +#include "test_utilities/log_capture.hpp" + +SCENARIO("Creating, saving and loading a transfer function", + "[phx][phx::TransferFunction]") { + GIVEN("A test transfer function") { + phx::TransferFunction transfer_function; + glm::vec4 red_color{1.0f, 0.0f, 0.0f, 0.0f}; + glm::vec4 black_color{0.0f, 0.0f, 0.0f, 0.0f}; + transfer_function.SetEntries({{0.0f, red_color}, {1.0f, black_color}}); + + WHEN("We save it and load it into a new one") { + transfer_function.Save("test_tranfer_function.json"); + phx::TransferFunction loaded_function; + loaded_function.Load("test_tranfer_function.json"); + THEN("The loaded one is identical to the original") { + for (auto i = 0u; i < transfer_function.GetEntries().size(); ++i) { + REQUIRE(transfer_function.GetEntries()[i] == + loaded_function.GetEntries()[i]); + } + } + } + + WHEN("We query for a scalar between two transfer function entries") { + THEN("The color is interpolated correctly") { + REQUIRE(transfer_function.Interpolate(0.0f) == red_color); + REQUIRE(transfer_function.Interpolate(1.0f) == black_color); + REQUIRE(transfer_function.Interpolate(0.5f) == + glm::vec4(0.5f, 0.0f, 0.0f, 0.0f)); + } + } + + WHEN("We query for a scalar outside of the transfer function range") { + THEN("The color is set to max/min and a warning is given.") { + auto log_capture = std::make_shared<test_utilities::LogCapture>(); + phx::logger = + std::make_shared<spdlog::logger>("logcapture", log_capture); + REQUIRE(transfer_function.Interpolate(-1.0f) == red_color); + REQUIRE(*log_capture == + "WARNING: Passed value is out of TransferFunction range! " + "Setting to minimum."); + + log_capture = std::make_shared<test_utilities::LogCapture>(); + phx::logger = + std::make_shared<spdlog::logger>("logcapture", log_capture); + REQUIRE(transfer_function.Interpolate(2.0f) == black_color); + REQUIRE(*log_capture == + "WARNING: Passed value is out of TransferFunction range! " + "Setting to maximum."); + } + } + + WHEN("Wechange the mode to nearest neighbor") { + transfer_function.SetMode( + phx::TransferFunction::InterpolationMode::NEAREST_NEIGHBOR); + THEN("The color is set correctly") { + REQUIRE(transfer_function.Interpolate(0.2f) == red_color); + REQUIRE(transfer_function.Interpolate(0.7f) == black_color); + REQUIRE(transfer_function.Interpolate(0.5f) == black_color); + } + } + } + + GIVEN("A test transfer function with only one value") { + phx::TransferFunction transfer_function; + glm::vec4 black_color{0.0f, 0.0f, 0.0f, 0.0f}; + transfer_function.SetEntries({{1.0f, black_color}}); + WHEN("We ask for values out of its range") { + THEN("The color is set to the only given transformation") { + REQUIRE(transfer_function.Interpolate(0.0f) == black_color); + REQUIRE(transfer_function.Interpolate(2.0f) == black_color); + } + } + } +}