1
\$\begingroup\$

This is a follow-up question for Tests for the operators of image template class in C++. As G. Sliepen's answer mentioned, I am attempting to use Boost.Test and several test cases are created with BOOST_AUTO_TEST_CASE_TEMPLATE structure.

The experimental implementation

  • Image template class implementation (image.h):

    /* Developed by Jimmy Hu */
    
    #ifndef Image_H
    #define Image_H
    
    #include <algorithm>
    #include <array>
    #include <cassert>
    #include <chrono>
    #include <complex>
    #include <concepts>
    #include <fstream>
    #include <functional>
    #include <iostream>
    #include <iterator>
    #include <list>
    #include <numeric>
    #include <string>
    #include <type_traits>
    #include <variant>
    #include <vector>
    
    #include "image_operations.h"
    
    #ifdef USE_BOOST_SERIALIZATION
    #include <boost/archive/binary_oarchive.hpp>
    #include <boost/archive/binary_iarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/array.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/serialization/export.hpp>
    #include <boost/serialization/list.hpp>
    #include <boost/serialization/nvp.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/split_free.hpp>
    #include <boost/serialization/unique_ptr.hpp>
    #include <boost/serialization/vector.hpp>
    #endif
    
    namespace TinyDIP
    {
        template <typename ElementT>
        class Image
        {
        public:
            Image() = default;
    
            Image(const std::size_t width, const std::size_t height):
                width(width),
                height(height),
                image_data(width * height) { }
    
            Image(const std::size_t width, const std::size_t height, const ElementT initVal):
                width(width),
                height(height),
                image_data(width * height, initVal) {}
    
            Image(const std::vector<ElementT>& input, std::size_t newWidth, std::size_t newHeight):
                width(newWidth),
                height(newHeight)
            {
                assert(input.size() == newWidth * newHeight);
                this->image_data = input;   //  Deep copy
            }
    
            Image(const std::vector<std::vector<ElementT>>& input)
            {
                this->height = input.size();
                this->width = input[0].size();
    
                for (auto& rows : input)
                {
                    this->image_data.insert(this->image_data.end(), std::begin(input), std::end(input));    //  flatten
                }
                return;
            }
    
            constexpr ElementT& at(const unsigned int x, const unsigned int y)
            { 
                checkBoundary(x, y);
                return this->image_data[y * width + x];
            }
    
            constexpr ElementT const& at(const unsigned int x, const unsigned int y) const
            {
                checkBoundary(x, y);
                return this->image_data[y * width + x];
            }
    
            constexpr std::size_t getWidth() const
            {
                return this->width;
            }
    
            constexpr std::size_t getHeight() const
            {
                return this->height;
            }
    
            constexpr auto getSize()
            {
                return std::make_tuple(this->width, this->height);
            }
    
            std::vector<ElementT> const& getImageData() const { return this->image_data; }      //  expose the internal data
    
            void print(std::string separator = "\t", std::ostream& os = std::cout)
            {
                for (std::size_t y = 0; y < this->height; ++y)
                {
                    for (std::size_t x = 0; x < this->width; ++x)
                    {
                        //  Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
                        os << +this->at(x, y) << separator;
                    }
                    os << "\n";
                }
                os << "\n";
                return;
            }
    
            //  Enable this function if ElementT = RGB
            void print(std::string separator = "\t", std::ostream& os = std::cout) requires(std::same_as<ElementT, RGB>)
            {
                for (std::size_t y = 0; y < this->height; ++y)
                {
                    for (std::size_t x = 0; x < this->width; ++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 << +this->at(x, y).channels[channel_index] << separator;
                        }
                        os << ")" << separator;
                    }
                    os << "\n";
                }
                os << "\n";
                return;
            }
    
            friend std::ostream& operator<<(std::ostream& os, const Image<ElementT>& rhs)
            {
                const std::string separator = "\t";
                for (std::size_t y = 0; y < rhs.height; ++y)
                {
                    for (std::size_t x = 0; x < rhs.width; ++x)
                    {
                        //  Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
                        os << +rhs.at(x, y) << separator;
                    }
                    os << "\n";
                }
                os << "\n";
                return os;
            }
    
            Image<ElementT>& operator+=(const Image<ElementT>& rhs)
            {
                assert(rhs.width == this->width);
                assert(rhs.height == this->height);
                std::transform(image_data.cbegin(), image_data.cend(), rhs.image_data.cbegin(),
                       image_data.begin(), std::plus<>{});
                return *this;
            }
    
            Image<ElementT>& operator-=(const Image<ElementT>& rhs)
            {
                assert(rhs.width == this->width);
                assert(rhs.height == this->height);
                std::transform(image_data.cbegin(), image_data.cend(), rhs.image_data.cbegin(),
                       image_data.begin(), std::minus<>{});
                return *this;
            }
    
            Image<ElementT>& operator*=(const Image<ElementT>& rhs)
            {
                assert(rhs.width == this->width);
                assert(rhs.height == this->height);
                std::transform(image_data.cbegin(), image_data.cend(), rhs.image_data.cbegin(),
                       image_data.begin(), std::multiplies<>{});
                return *this;
            }
    
            Image<ElementT>& operator/=(const Image<ElementT>& rhs)
            {
                assert(rhs.width == this->width);
                assert(rhs.height == this->height);
                std::transform(image_data.cbegin(), image_data.cend(), rhs.image_data.cbegin(),
                       image_data.begin(), std::divides<>{});
                return *this;
            }
    
            bool operator==(const Image<ElementT>& rhs) const
            {
                /* do actual comparison */
                if (rhs.width != this->width ||
                    rhs.height != this->height)
                {
                    return false;
                }
                return rhs.image_data == this->image_data;
            }
    
            bool operator!=(const Image<ElementT>& rhs) const
            {
                return !(this == rhs);
            }
    
            Image<ElementT>& operator=(Image<ElementT> const& input) = default;  //  Copy Assign
    
            Image<ElementT>& operator=(Image<ElementT>&& other) = default;       //  Move Assign
    
            Image(const Image<ElementT> &input) = default;                       //  Copy Constructor
    
            Image(Image<ElementT> &&input) = default;                            //  Move Constructor
    
    #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::size_t width;
            std::size_t height;
            std::vector<ElementT> image_data;
    
            void checkBoundary(const size_t x, const size_t y) const
            {
                assert(x < width);
                assert(y < height);
            }
    
    #ifdef USE_BOOST_SERIALIZATION
            friend class boost::serialization::access;
            template<class Archive>
            void serialize(Archive& ar, const unsigned int version)
            {
                ar& width;
                ar& height;
                ar& image_data;
            }
    #endif
    
        };
    }
    
    #endif
    
  • image_operations.h:

    /* Developed by Jimmy Hu */
    
    #ifndef ImageOperations_H
    #define ImageOperations_H
    
    #include <numbers>
    #include <string>
    #include "base_types.h"
    #include "image.h"
    
    #define is_size_same(x, y) {assert(x.getWidth() == y.getWidth()); assert(x.getHeight() == y.getHeight());}
    
    namespace TinyDIP
    {
        // Forward Declaration class Image
        template <typename ElementT>
        class Image;
    
        template<class T = GrayScale>
        requires (std::same_as<T, GrayScale>)
        constexpr static auto constructRGB(Image<T> r, Image<T> g, Image<T> b)
        {
            is_size_same(r, g);
            is_size_same(g, b);
            is_size_same(r, b);
            return;
        }
    
        template<typename T>
        T normalDistribution1D(const T x, const T standard_deviation)
        {
            return std::exp(-x * x / (2 * standard_deviation * standard_deviation));
        }
    
        template<typename T>
        T normalDistribution2D(const T xlocation, const T ylocation, const T standard_deviation)
        {
            return std::exp(-(xlocation * xlocation + ylocation * ylocation) / (2 * standard_deviation * standard_deviation)) / (2 * std::numbers::pi * standard_deviation * standard_deviation);
        }
    
        template<class InputT1, class InputT2>
        constexpr static auto cubicPolate(const InputT1 v0, const InputT1 v1, const InputT1 v2, const InputT1 v3, const InputT2 frac)
        {
            auto A = (v3-v2)-(v0-v1);
            auto B = (v0-v1)-A;
            auto C = v2-v0;
            auto D = v1;
            return D + frac * (C + frac * (B + frac * A));
        }
    
        template<class InputT = float, class ElementT>
        constexpr static auto bicubicPolate(const ElementT* const ndata, const InputT fracx, const InputT fracy)
        {
            auto x1 = cubicPolate( ndata[0], ndata[1], ndata[2], ndata[3], fracx );
            auto x2 = cubicPolate( ndata[4], ndata[5], ndata[6], ndata[7], fracx );
            auto x3 = cubicPolate( ndata[8], ndata[9], ndata[10], ndata[11], fracx );
            auto x4 = cubicPolate( ndata[12], ndata[13], ndata[14], ndata[15], fracx );
    
            return std::clamp(
                        cubicPolate( x1, x2, x3, x4, fracy ), 
                        static_cast<InputT>(std::numeric_limits<ElementT>::min()),
                        static_cast<InputT>(std::numeric_limits<ElementT>::max()));
        }
    
        template<class FloatingType = float, class ElementT>
        Image<ElementT> copyResizeBicubic(Image<ElementT>& image, size_t width, size_t height)
        {
            auto output = Image<ElementT>(width, height);
            //  get used to the C++ way of casting
            auto ratiox = static_cast<FloatingType>(image.getWidth()) / static_cast<FloatingType>(width);
            auto ratioy = static_cast<FloatingType>(image.getHeight()) / static_cast<FloatingType>(height);
    
            for (size_t y = 0; y < height; ++y)
            {
                for (size_t x = 0; x < width; ++x)
                {
                    FloatingType xMappingToOrigin = static_cast<FloatingType>(x) * ratiox;
                    FloatingType yMappingToOrigin = static_cast<FloatingType>(y) * ratioy;
                    FloatingType xMappingToOriginFloor = std::floor(xMappingToOrigin);
                    FloatingType yMappingToOriginFloor = std::floor(yMappingToOrigin);
                    FloatingType xMappingToOriginFrac = xMappingToOrigin - xMappingToOriginFloor;
                    FloatingType yMappingToOriginFrac = yMappingToOrigin - yMappingToOriginFloor;
    
                    ElementT ndata[4 * 4];
                    for (int ndatay = -1; ndatay <= 2; ++ndatay)
                    {
                        for (int ndatax = -1; ndatax <= 2; ++ndatax)
                        {
                            ndata[(ndatay + 1) * 4 + (ndatax + 1)] = image.at(
                                std::clamp(xMappingToOriginFloor + ndatax, static_cast<FloatingType>(0), image.getWidth() - static_cast<FloatingType>(1)), 
                                std::clamp(yMappingToOriginFloor + ndatay, static_cast<FloatingType>(0), image.getHeight() - static_cast<FloatingType>(1)));
                        }
    
                    }
                    output.at(x, y) = bicubicPolate(ndata, xMappingToOriginFrac, yMappingToOriginFrac);
                }
            }
            return output;
        }
    
        //  multiple standard deviations
        template<class InputT>
        constexpr static Image<InputT> gaussianFigure2D(
            const size_t xsize, const size_t ysize, 
            const size_t centerx, const size_t centery,
            const InputT standard_deviation_x, const InputT standard_deviation_y)
        {
            auto output = TinyDIP::Image<InputT>(xsize, ysize);
            auto row_vector_x = TinyDIP::Image<InputT>(xsize, 1);
            for (size_t x = 0; x < xsize; ++x)
            {
                row_vector_x.at(x, 0) = normalDistribution1D(static_cast<InputT>(x) - static_cast<InputT>(centerx), standard_deviation_x);
            }
    
            auto row_vector_y = TinyDIP::Image<InputT>(ysize, 1);
            for (size_t y = 0; y < ysize; ++y)
            {
                row_vector_y.at(y, 0) = normalDistribution1D(static_cast<InputT>(y) - static_cast<InputT>(centery), standard_deviation_y);
            }
    
            for (size_t y = 0; y < ysize; ++y)
            {
                for (size_t x = 0; x < xsize; ++x)
                {
                    output.at(x, y) = row_vector_x.at(x, 0) * row_vector_y.at(y, 0);
                }
            }
            return output;
        }
    
        //  single standard deviation
        template<class InputT>
        constexpr static Image<InputT> gaussianFigure2D(
            const size_t xsize, const size_t ysize,
            const size_t centerx, const size_t centery,
            const InputT standard_deviation)
        {
            return gaussianFigure2D(xsize, ysize, centerx, centery, standard_deviation, standard_deviation);
        }
    
        template<typename Op, class InputT, class... Args>
        constexpr static Image<InputT> pixelwiseOperation(Op op, const Image<InputT>& input1, const Args&... inputs)
        {
            Image<InputT> output(
                recursive_transform<1>(
                    [&](auto&& element1, auto&&... elements) 
                        {
                            auto result = op(element1, elements...);
                            return static_cast<InputT>(std::clamp(
                                result,
                                static_cast<decltype(result)>(std::numeric_limits<InputT>::min()),
                                static_cast<decltype(result)>(std::numeric_limits<InputT>::max())));
                        },
                    (input1.getImageData()),
                    (inputs.getImageData())...),
                input1.getWidth(),
                input1.getHeight());
            return output;
        }
    
        template<class InputT>
        constexpr static Image<InputT> plus(const Image<InputT>& input1)
        {
            return input1;
        }
    
        template<class InputT, class... Args>
        constexpr static Image<InputT> plus(const Image<InputT>& input1, const Args&... inputs)
        {
            return TinyDIP::pixelwiseOperation(std::plus<>{}, input1, plus(inputs...));
        }
    
        template<class InputT>
        constexpr static Image<InputT> subtract(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            is_size_same(input1, input2);
            return TinyDIP::pixelwiseOperation(std::minus<>{}, input1, input2);
        }
    
        template<class InputT = RGB>
        requires (std::same_as<InputT, RGB>)
        constexpr static Image<InputT> subtract(Image<InputT>& input1, Image<InputT>& input2)
        {
            is_size_same(input1, input2);
            Image<InputT> output(input1.getWidth(), input1.getHeight());
            for (std::size_t y = 0; y < input1.getHeight(); ++y)
            {
                for (std::size_t x = 0; x < input1.getWidth(); ++x)
                {
                    for(std::size_t channel_index = 0; channel_index < 3; ++channel_index)
                    {
                        output.at(x, y).channels[channel_index] = 
                        std::clamp(
                            input1.at(x, y).channels[channel_index] - 
                            input2.at(x, y).channels[channel_index],
                            0,
                            255);
                    }
                }
            }
            return output;
        }
    
        template<class InputT>
        constexpr static Image<InputT> multiplies(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            return TinyDIP::pixelwiseOperation(std::multiplies<>{}, input1, input2);
        }
    
        template<class InputT, class... Args>
        constexpr static Image<InputT> multiplies(const Image<InputT>& input1, const Args&... inputs)
        {
            return TinyDIP::pixelwiseOperation(std::multiplies<>{}, input1, multiplies(inputs...));
        }
    
        template<class InputT>
        constexpr static Image<InputT> divides(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            return TinyDIP::pixelwiseOperation(std::divides<>{}, input1, input2);
        }
    
        template<class InputT>
        constexpr static Image<InputT> modulus(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            return TinyDIP::pixelwiseOperation(std::modulus<>{}, input1, input2);
        }
    
        template<class InputT>
        constexpr static Image<InputT> negate(const Image<InputT>& input1, const Image<InputT>& input2)
        {
            return TinyDIP::pixelwiseOperation(std::negate<>{}, input1);
        }
    }
    
    #endif
    
  • base_types.h: The base types

    /* Developed by Jimmy Hu */
    
    #ifndef BASE_H
    #define BASE_H
    
    #include <filesystem>
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string>
    #include <utility>
    
    using BYTE = unsigned char;
    
    struct RGB
    {
        BYTE channels[3];
    };
    
    using GrayScale = BYTE;
    
    struct HSV
    {
        double channels[3];    //  Range: 0 <= H < 360, 0 <= S <= 1, 0 <= V <= 255
    };
    
    struct BMPIMAGE
    {
        std::filesystem::path FILENAME;
    
        unsigned int XSIZE;
        unsigned int YSIZE;
        BYTE FILLINGBYTE;
        BYTE *IMAGE_DATA;
    };
    #endif
    

Unit Tests with Boost.Test

#define BOOST_TEST_DYN_LINK

#define BOOST_TEST_MODULE image_elementwise_tests

#ifdef BOOST_TEST_MODULE
#include <boost/test/included/unit_test.hpp>


#ifdef BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#else
#include <boost/test/included/unit_test.hpp>
#endif // BOOST_TEST_DYN_LINK

#include <boost/mpl/list.hpp>
#include <boost/mpl/vector.hpp>
#include <tao/tuple/tuple.hpp>

typedef boost::mpl::list<
    byte, char, int, short, long, long long int,
    unsigned int, unsigned short int, unsigned long int, unsigned long long int,
    float, double, long double> test_types;

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_add_test, T, test_types)
{
    std::size_t size_x = 10;
    std::size_t size_y = 10;
    T initVal = 10;
    T increment = 1;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test += TinyDIP::Image<T>(size_x, size_y, increment);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal + increment));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_add_test_zero_dimensions, T, test_types)
{
    std::size_t size_x = 0;                         //  Test images with both of the dimensions having size zero.
    std::size_t size_y = 0;                         //  Test images with both of the dimensions having size zero.
    T initVal = 10;
    T increment = 1;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test += TinyDIP::Image<T>(size_x, size_y, increment);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal + increment));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_add_test_large_dimensions, T, test_types)
{
    std::size_t size_x = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    std::size_t size_y = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    T initVal = 10;
    T increment = 1;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test += TinyDIP::Image<T>(size_x, size_y, increment);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal + increment));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_minus_test, T, test_types)
{
    std::size_t size_x = 10;
    std::size_t size_y = 10;
    T initVal = 10;
    T difference = 1;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test -= TinyDIP::Image<T>(size_x, size_y, difference);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal - difference));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_minus_test_zero_dimensions, T, test_types)
{
    std::size_t size_x = 0;                         //  Test images with both of the dimensions having size zero.
    std::size_t size_y = 0;                         //  Test images with both of the dimensions having size zero.
    T initVal = 10;
    T difference = 1;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test -= TinyDIP::Image<T>(size_x, size_y, difference);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal - difference));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_minus_test_large_dimensions, T, test_types)
{
    std::size_t size_x = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    std::size_t size_y = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    T initVal = 10;
    T difference = 1;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test -= TinyDIP::Image<T>(size_x, size_y, difference);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal - difference));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_multiplies_test, T, test_types)
{
    std::size_t size_x = 10;
    std::size_t size_y = 10;
    T initVal = 10;
    T multiplier = 2;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test *= TinyDIP::Image<T>(size_x, size_y, multiplier);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal * multiplier));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_multiplies_test_zero_dimensions, T, test_types)
{
    std::size_t size_x = 0;                         //  Test images with both of the dimensions having size zero.
    std::size_t size_y = 0;                         //  Test images with both of the dimensions having size zero.
    T initVal = 10;
    T multiplier = 2;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test *= TinyDIP::Image<T>(size_x, size_y, multiplier);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal * multiplier));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_multiplies_test_large_dimensions, T, test_types)
{
    std::size_t size_x = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    std::size_t size_y = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    T initVal = 10;
    T multiplier = 2;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test *= TinyDIP::Image<T>(size_x, size_y, multiplier);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal * multiplier));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_divides_test, T, test_types)
{
    std::size_t size_x = 10;
    std::size_t size_y = 10;
    T initVal = 10;
    T divider = 2;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test /= TinyDIP::Image<T>(size_x, size_y, divider);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal / divider));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_divides_test_zero_dimensions, T, test_types)
{
    std::size_t size_x = 0;                         //  Test images with both of the dimensions having size zero.
    std::size_t size_y = 0;                         //  Test images with both of the dimensions having size zero.
    T initVal = 10;
    T divider = 2;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test /= TinyDIP::Image<T>(size_x, size_y, divider);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal / divider));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_divides_test_large_dimensions, T, test_types)
{
    std::size_t size_x = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    std::size_t size_y = 18446744073709551615;       //  Test images with very large dimensions (std::numeric_limits<std::size_t>::max()).
    T initVal = 10;
    T divider = 2;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test /= TinyDIP::Image<T>(size_x, size_y, divider);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal / divider));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_divides_zero_test, T, test_types)
{
    std::size_t size_x = 10;
    std::size_t size_y = 10;
    T initVal = 10;
    T divider = 0;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    test /= TinyDIP::Image<T>(size_x, size_y, divider);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, initVal / divider));       //  dividing by zero test
}

#endif

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    Tests for the operators of image template class in C++

  • What changes has been made in the code since last question?

    I am attempting to create some test cases with Boost.Test framework in this post.

  • Why a new review is being asked for?

    If there is any possible improvement, please let me know.

\$\endgroup\$
3
  • \$\begingroup\$ What will this cost be used for? Is it just practice for using boost.test? \$\endgroup\$
    – Emily L.
    Commented Aug 15, 2021 at 23:54
  • \$\begingroup\$ @EmilyL. Thank you for your comments. Both practice and validation of the functions. Do you have any suggestions? \$\endgroup\$
    – JimmyHu
    Commented Aug 16, 2021 at 23:49
  • 3
    \$\begingroup\$ Just an observation that if you're doing this for practice then fair enough but if you're building this for production, I'd recommend using an existing library instead to save the time and get more features and likely more performance as your code seems very bare bones at the moment. \$\endgroup\$
    – Emily L.
    Commented Aug 18, 2021 at 16:17

1 Answer 1

2
\$\begingroup\$

Ensure you cover the basics

You have a lot of tests that check the operations on already created TinyDIP::Images, but you lack tests to check that the creation and initialization of images works as expected. I would add something like this at least:

BOOST_AUTO_TEST_CASE_TEMPLATE(image_creation, T, test_types)
{
    std::size_t size_x = 10;
    std::size_t size_y = 10;
    T initVal = 10;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    for (std::size_t y = 0; y < size_y; ++y) {
        for (std::size_t x = 0; x < size_x; ++x) {
            BOOST_TEST(test.at(x, y) == initval);
        }
    }
}

You also might want to check that images remember different values for different pixels, and that operations on them also give the right result, and not accidentily swap x and y coordinates for example.

Aim for 100% code coverage by your test suite

I see a lot of public functions not being tested in your test suite. Ideally, your test suite covers all your code. Code coverage tools can help you track how much of the code is tested by your test suite. They might have a hard time with templates though, but have a look at these StackOverflow questions for some pointers:

Avoid code duplication

I see quite a lot of code duplication. The test cases all look the same, except for size_x and size_y being different, and the operation on test being different. Boost.Test has macros for running the same test with different values, perhaps that can be combined some way with the macros for using different template types. Otherwise, consider writing a generic function like so:

template<typename T, typename Op>
static void test_image_operation(std::size_t size, T operand, Op op) {
    std::size_t size_x = size;
    std::size_t size_y = size;
    T initVal = 10;
    auto test = TinyDIP::Image<T>(size_x, size_y, initVal);
    op(test, operand);
    T finalVal = initVal;
    op(finalVal, operand);
    BOOST_TEST(test == TinyDIP::Image<T>(size_x, size_y, finalVal));
}

And call it like so:

BOOST_AUTO_TEST_CASE_TEMPLATE(image_elementwise_add_test, T, test_types)
{
    test_image_operation<T>(10, 1, [](auto &lhs, auto &rhs){ lhs += rhs; });
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.