This is a follow-up question for draw_if_possible Template Function Implementation for Image in C++. In G. Sliepen's answer, set
member function has been mentioned. I am trying to add set
member function implementation into the Image
class. The first parameter of set
function is the location encapsulated in std::tuple
structure. The second parameter is the element value to be set in the given location. The return value represents set
operation run successfully or not. What do you think about using std::tuple
for location presentation?
The experimental implementation
set
member function implementation (in fileimage.h
)// set template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr bool set(const TupleT location, const ElementT draw_value) { if (checkBoundaryTuple(location)) { image_data[tuple_location_to_index(location)] = draw_value; return true; } return false; }
is_tuple
struct implementation (in filebasic_functions.h
)// Reference: https://stackoverflow.com/a/48458312/6667035 template <typename> struct is_tuple : std::false_type {}; template <typename ...T> struct is_tuple<std::tuple<T...>> : std::true_type {};
The full
Image
class implementationnamespace TinyDIP { template <typename ElementT> class Image { public: Image() = default; template<std::same_as<std::size_t>... Sizes> Image(Sizes... sizes): size{sizes...}, image_data((1 * ... * sizes)) {} template<std::same_as<int>... Sizes> Image(Sizes... sizes) { size.reserve(sizeof...(sizes)); (size.emplace_back(sizes), ...); image_data.resize( std::reduce( std::ranges::cbegin(size), std::ranges::cend(size), std::size_t{1}, std::multiplies<>() ) ); } Image(const std::vector<std::size_t>& sizes) { size = std::move(sizes); image_data.resize( std::reduce( std::ranges::cbegin(sizes), std::ranges::cend(sizes), std::size_t{1}, std::multiplies<>() )); } template<std::ranges::input_range Range, std::same_as<std::size_t>... Sizes> Image(const Range& input, Sizes... sizes): size{sizes...}, image_data(begin(input), end(input)) { if (image_data.size() != (1 * ... * sizes)) { throw std::runtime_error("Image data input and the given size are mismatched!"); } } template<std::same_as<std::size_t>... Sizes> Image(std::vector<ElementT>&& input, Sizes... sizes): size{sizes...}, image_data(begin(input), end(input)) { if (image_data.size() != (1 * ... * sizes)) { throw std::runtime_error("Image data input and the given size are mismatched!"); } } Image(const std::vector<ElementT>& input, const std::vector<std::size_t>& sizes) { size = std::move(sizes); image_data = std::move(input); auto count = std::reduce(std::ranges::cbegin(sizes), std::ranges::cend(sizes), 1, std::multiplies()); if (image_data.size() != count) { throw std::runtime_error("Image data input and the given size are mismatched!"); } } Image(const std::vector<ElementT>& input, std::size_t newWidth, std::size_t newHeight) { size.reserve(2); size.emplace_back(newWidth); size.emplace_back(newHeight); if (input.size() != newWidth * newHeight) { throw std::runtime_error("Image data input and the given size are mismatched!"); } image_data = std::move(input); // Reference: https://stackoverflow.com/a/51706522/6667035 } Image(const std::vector<std::vector<ElementT>>& input) { size.reserve(2); size.emplace_back(input[0].size()); size.emplace_back(input.size()); for (auto& rows : input) { image_data.insert(image_data.end(), std::ranges::begin(input), std::ranges::end(input)); // flatten } return; } // at template function implementation template<typename... Args> constexpr ElementT& at(const Args... indexInput) { return const_cast<ElementT&>(static_cast<const Image &>(*this).at(indexInput...)); } // at template function implementation // Reference: https://codereview.stackexchange.com/a/288736/231235 template<typename... Args> constexpr ElementT const& at(const Args... indexInput) const { checkBoundary(indexInput...); constexpr std::size_t n = sizeof...(Args); if(n != size.size()) { throw std::runtime_error("Dimensionality mismatched!"); } std::size_t i = 0; std::size_t stride = 1; std::size_t position = 0; auto update_position = [&](auto index) { position += index * stride; stride *= size[i++]; }; (update_position(indexInput), ...); return image_data[position]; } // at_without_boundary_check template function implementation template<typename... Args> constexpr ElementT& at_without_boundary_check(const Args... indexInput) { return const_cast<ElementT&>(static_cast<const Image &>(*this).at_without_boundary_check(indexInput...)); } template<typename... Args> constexpr ElementT const& at_without_boundary_check(const Args... indexInput) const { std::size_t i = 0; std::size_t stride = 1; std::size_t position = 0; auto update_position = [&](auto index) { position += index * stride; stride *= size[i++]; }; (update_position(indexInput), ...); return image_data[position]; } // get function implementation constexpr ElementT get(std::size_t index) const noexcept { return image_data[index]; } // set function implementation constexpr ElementT& set(const std::size_t index) noexcept { return image_data[index]; } // set template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr bool set(const TupleT location, const ElementT draw_value) { if (checkBoundaryTuple(location)) { image_data[tuple_location_to_index(location)] = draw_value; return true; } return false; } // cast template function implementation template<typename TargetT> constexpr Image<TargetT> cast() { std::vector<TargetT> output_data; output_data.resize(image_data.size()); std::transform( std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::begin(output_data), [](auto& input){ return static_cast<TargetT>(input); } ); Image<TargetT> output(output_data, size); return output; } constexpr std::size_t count() const noexcept { return std::reduce(std::ranges::cbegin(size), std::ranges::cend(size), 1, std::multiplies()); } constexpr std::size_t getDimensionality() const noexcept { return size.size(); } constexpr std::size_t getWidth() const noexcept { return size[0]; } constexpr std::size_t getHeight() const noexcept { return size[1]; } // getSize function implementation constexpr auto getSize() const noexcept { return size; } // getSize function implementation constexpr auto getSize(std::size_t index) const noexcept { return size[index]; } // getStride function implementation constexpr std::size_t getStride(std::size_t index) const noexcept { if(index == 0) { return std::size_t{1}; } std::size_t output = std::size_t{1}; for(std::size_t i = 0; i < index; ++i) { output *= size[i]; } return output; } std::vector<ElementT> const& getImageData() const noexcept { return image_data; } // expose the internal data // print function implementation void print(std::string separator = "\t", std::ostream& os = std::cout) const { if(size.size() == 1) { for(std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x) << separator; } os << "\n"; } else if(size.size() == 2) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y) << separator; } os << "\n"; } os << "\n"; } else if (size.size() == 3) { for(std::size_t z = 0; z < size[2]; ++z) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y, z) << separator; } os << "\n"; } os << "\n"; } os << "\n"; } else if (size.size() == 4) { for(std::size_t a = 0; a < size[3]; ++a) { os << "group = " << a << "\n"; for(std::size_t z = 0; z < size[2]; ++z) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y, z, a) << separator; } os << "\n"; } os << "\n"; } os << "\n"; } os << "\n"; } } // Enable this function if ElementT = RGB void print(std::string separator = "\t", std::ostream& os = std::cout) const requires(std::same_as<ElementT, RGB>) { for (std::size_t y = 0; y < size[1]; ++y) { for (std::size_t x = 0; x < size[0]; ++x) { os << "( "; for (std::size_t channel_index = 0; channel_index < 3; ++channel_index) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y).channels[channel_index] << separator; } os << ")" << separator; } os << "\n"; } os << "\n"; return; } Image<ElementT>& setAllValue(const ElementT input) { std::fill(std::ranges::begin(image_data), std::ranges::end(image_data), input); return *this; } friend std::ostream& operator<<(std::ostream& os, const Image<ElementT>& rhs) { const std::string separator = "\t"; rhs.print(separator, os); return os; } Image<ElementT>& operator+=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::plus<>{}); return *this; } Image<ElementT>& operator-=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::minus<>{}); return *this; } Image<ElementT>& operator*=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::multiplies<>{}); return *this; } Image<ElementT>& operator/=(const Image<ElementT>& rhs) { check_size_same(rhs, *this); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::divides<>{}); return *this; } friend bool operator==(Image<ElementT> const&, Image<ElementT> const&) = default; friend bool operator!=(Image<ElementT> const&, Image<ElementT> const&) = default; friend Image<ElementT> operator+(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 += input2; } friend Image<ElementT> operator-(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 -= input2; } friend Image<ElementT> operator*(Image<ElementT> input1, ElementT input2) { return multiplies(input1, input2); } friend Image<ElementT> operator*(ElementT input1, Image<ElementT> input2) { return multiplies(input2, input1); } #ifdef USE_BOOST_SERIALIZATION void Save(std::string filename) { const std::string filename_with_extension = filename + ".dat"; // Reference: https://stackoverflow.com/questions/523872/how-do-you-serialize-an-object-in-c std::ofstream ofs(filename_with_extension, std::ios::binary); boost::archive::binary_oarchive ArchiveOut(ofs); // write class instance to archive ArchiveOut << *this; // archive and stream closed when destructors are called ofs.close(); } #endif private: std::vector<std::size_t> size; std::vector<ElementT> image_data; template<typename... Args> void checkBoundary(const Args... indexInput) const { constexpr std::size_t n = sizeof...(Args); if(n != size.size()) { throw std::runtime_error("Dimensionality mismatched!"); } std::size_t parameter_pack_index = 0; auto function = [&](auto index) { if (index >= size[parameter_pack_index]) throw std::out_of_range("Given index out of range!"); parameter_pack_index = parameter_pack_index + 1; }; (function(indexInput), ...); } // checkBoundaryTuple template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr bool checkBoundaryTuple(const TupleT location) { constexpr std::size_t n = std::tuple_size<TupleT>{}; if(n != size.size()) { throw std::runtime_error("Dimensionality mismatched!"); } std::size_t parameter_pack_index = 0; auto function = [&](auto index) { if (std::cmp_greater_equal(index, size[parameter_pack_index])) return false; parameter_pack_index = parameter_pack_index + 1; return true; }; return std::apply([&](auto&&... args) { return ((function(args))&& ...);}, location); } // tuple_location_to_index template function implementation template<class TupleT> requires(is_tuple<TupleT>::value) constexpr std::size_t tuple_location_to_index(TupleT location) { std::size_t i = 0; std::size_t stride = 1; std::size_t position = 0; auto update_position = [&](auto index) { position += index * stride; stride *= size[i++]; }; std::apply([&](auto&&... args) {((update_position(args)), ...);}, location); return position; } #ifdef USE_BOOST_SERIALIZATION friend class boost::serialization::access; template<class Archive> void serialize(Archive& ar, const unsigned int version) { ar& size; ar& image_data; } #endif }; template<typename T, typename ElementT> concept is_Image = std::is_same_v<T, Image<ElementT>>; }
The usage of set
member function:
// draw_circle template function implementation
// https://codereview.stackexchange.com/q/293417/231235
// Test: https://godbolt.org/z/7zKfhG3x9
// Test with output.set: https://godbolt.org/z/1GYdrbs5q
template<typename ElementT>
constexpr static auto draw_circle(
const Image<ElementT>& input,
std::tuple<std::size_t, std::size_t> central_point,
std::size_t radius = 2,
ElementT draw_value = ElementT{}
)
{
if (input.getDimensionality() != 2)
{
throw std::runtime_error("Unsupported dimension!");
}
auto point_x = std::get<0>(central_point);
auto point_y = std::get<1>(central_point);
auto output = input;
auto height = input.getHeight();
auto width = input.getWidth();
if (radius <= 0)
{
// early out avoids y going negative in loop
return output;
}
for (std::ptrdiff_t x = 0, y = radius; x <= y; x++)
{
// try to decrement y, then accept or revert
y--;
if (x * x + y * y < radius * radius)
{
y++;
}
// do nothing if out of bounds, otherwise draw
output.set(std::make_tuple(point_x + x, point_y + y), draw_value);
output.set(std::make_tuple(point_x - x, point_y + y), draw_value);
output.set(std::make_tuple(point_x + x, point_y - y), draw_value);
output.set(std::make_tuple(point_x - x, point_y - y), draw_value);
output.set(std::make_tuple(point_x + y, point_y + x), draw_value);
output.set(std::make_tuple(point_x - y, point_y + x), draw_value);
output.set(std::make_tuple(point_x + y, point_y - x), draw_value);
output.set(std::make_tuple(point_x - y, point_y - x), draw_value);
}
return output;
}
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
draw_if_possible Template Function Implementation for Image in C++
What changes has been made in the code since last question?
I am trying to add
set
member function implementation into theImage
class.Why a new review is being asked for?
Please review the implementation of
set
member function.