What is the easiest way to implement compatibility layer where types don't agree in padding?

Issue #354 resolved
Olzhas Zhumabek created an issue

I hope I'm using the correct communication channel for this question. I didn’t find question issue type, so I put the least urgent one.

So basically I would like CustomMatrix to work on GIL images, but since pixel type and static vector don't agree in sizes, it will not work. For example:

gil::rgb8_pixel_t vs blaze::StaticVector<uint8_t, 3>

If I turn off the padding and alignment, there will still be mismatch in the sizes, as the pixel uses padding to 4 bytes. Turning the flags off does work for types like rgba8_pixel_t which takes up the same amount of space as the sum of channels.

Is there an easy route to implement a proxy that will behave like StaticVector<T, N>, but inherit from StaticVector<T, M> where M > N? I was thinking if I could inherit from a wider vector and then make it think it has less elements than it actually has space for, even with padding and alignment turned off. Will replacing the size() function with my own be enough to do this?

Comments (6)

  1. Klaus Iglberger

    Hi Olzhas!

    The question is very hard to answer since we don’t know what you are planning to do with the vectors and matrices. We assume that you are not interested in vectorization, but that you just want to represent the 4-byte data type gil::rgb8_pixel_t as Blaze vector. Then you are correct, StaticVector doesn’t fit your bill since its size and capacity are always equal (same as a std::array) and it uses padding only to facilitate vectorization. For a 1-byte data type such as uint8_t this will result in at least 16 elements. Inheriting from StaticVector would not work either. StaticVector is implemented by means of CRTP and down-casts would not know about your type. This means internally StaticVector::size() would be called.

    The most promising approach seems to be to use a custom data type (see the last post in issue #349 for the most recent explanation of this topic):

    DynamicMatrix<gil::rgb8_pixel_t> pixels( ... );
    

    Depending on which operations you have in mind, you would just have to provide the necessary overloads for gil::rgb8_pixel_t. For instance:

    gil::rgb8_pixel_t operator+( gil::rgb8_pixel_t a, gil::rgb8_pixel_t b ) { /*...*/ }
    
    DynamicMatrix<gil::rgb8_pixel_t> A, B;
    
    A + B;  // Enabled via operator+() for gil::rgb8_pixel_t
    

    I hope this helps,

    Best regards,

    Klaus!

  2. Olzhas Zhumabek reporter

    Thanks for the pointers. I will try out an adaptor I have in mind and come back with the results (will resolve the issue as well).

  3. Olzhas Zhumabek reporter

    I was able to make it compile for the Schur product case when it is an element of a matrix.

    #include <blaze/Blaze.h>
    #include <boost/gil/pixel.hpp>
    #include <functional>
    #include <type_traits>
    
    template <typename ChannelValue, typename Layout, bool IsRowVector = blaze::rowVector>
    struct pixel_vector
        : boost::gil::pixel<ChannelValue, Layout>,
          blaze::DenseVector<pixel_vector<ChannelValue, Layout, IsRowVector>, IsRowVector> {
        using parent_t = boost::gil::pixel<ChannelValue, Layout>;
        using boost::gil::pixel<ChannelValue, Layout>::pixel;
    
        using This = pixel_vector<ChannelValue, Layout, IsRowVector>;
        using Basetype = blaze::DenseVector<This, IsRowVector>;
        using ResultType = This;
    
        using TransposeType = pixel_vector<ChannelValue, Layout, !IsRowVector>;
    
        static constexpr bool simdEnabled = false;
    
        using ElementType = ChannelValue;
        using TagType = blaze::Group0;
        using ReturnType = const ChannelValue&;
        using CompositeType = const This&;
    
        using Reference = ChannelValue&;
        using ConstReference = const ChannelValue&;
        using Pointer = ChannelValue*;
        using ConstPointer = const ChannelValue*;
    
        // TODO: rule of 5
        template <typename OtherChannelValue>
        pixel_vector(pixel_vector<OtherChannelValue, Layout> other) : parent_t(other)
        {
        }
    
        template <typename OtherChannelValue>
        pixel_vector& operator=(const pixel_vector<OtherChannelValue, Layout> other)
        {
            parent_t::operator=(other);
            return *this;
        }
    
        template <typename VT, bool TransposeFlag>
        pixel_vector& operator=(const blaze::DenseVector<VT, TransposeFlag>& v)
        {
            if ((~v).size() != size()) {
                throw std::invalid_argument(
                    "incoming vector has incompatible size with this pixel vector");
            }
    
            for (std::size_t i = 0; i < size(); ++i) {
                (*this)[i] = (~v)[i];
            }
    
            return *this;
        }
    
      private:
        template <typename OtherChannelValue, typename Op>
        void perform_op(pixel_vector<OtherChannelValue, Layout> p, Op op)
        {
            constexpr auto num_channels = boost::gil::num_channels<parent_t>{};
            auto& current = *this;
            for (std::ptrdiff_t i = 0; i < num_channels; ++i) {
                current[i] = op(current[i], p[i]);
            }
        }
    
      public:
        std::size_t size() const { return boost::gil::num_channels<parent_t>{}; }
    
        constexpr bool canAlias() const { return true; }
    
        template <typename Other>
        bool isAliased(const Other* alias) const noexcept
        {
            return static_cast<const void*>(this) == static_cast<const void*>(alias);
        }
    
        template <typename Other>
        bool canAlias(const Other* alias) const noexcept
        {
            return static_cast<const void*>(this) == static_cast<const void*>(alias);
        }
    
        // TODO: Add SFINAE for cases when parent_t::is_mutable is false
        template <typename OtherChannelValue, typename OtherLayout>
        pixel_vector& operator+=(pixel_vector<OtherChannelValue, OtherLayout> other)
        {
            perform_op(other, std::plus<>{});
            return *this;
        }
    
        template <typename OtherChannelValue, typename OtherLayout>
        pixel_vector& operator-=(pixel_vector<OtherChannelValue, OtherLayout> other)
        {
            perform_op(other, std::minus<>{});
            return *this;
        }
    
        template <typename OtherChannelValue, typename OtherLayout>
        pixel_vector& operator*=(pixel_vector<OtherChannelValue, OtherLayout> other)
        {
            perform_op(other, std::multiplies<>{});
            return *this;
        }
    
        template <typename OtherChannelValue, typename OtherLayout>
        pixel_vector& operator/=(pixel_vector<OtherChannelValue, OtherLayout> other)
        {
            perform_op(other, std::divides<>{});
            return *this;
        }
    };
    
    template <typename ChannelValue1, typename ChannelValue2, typename Layout, bool IsRowVector>
    inline pixel_vector<std::common_type_t<ChannelValue1, ChannelValue2>, Layout>
    operator+(pixel_vector<ChannelValue1, Layout, IsRowVector> lhs,
              pixel_vector<ChannelValue2, Layout, IsRowVector> rhs)
    {
        lhs += rhs;
        return lhs;
    }
    
    template <typename ChannelValue1, typename ChannelValue2, typename Layout, bool IsRowVector>
    inline pixel_vector<std::common_type_t<ChannelValue1, ChannelValue2>, Layout>
    operator-(pixel_vector<ChannelValue1, Layout, IsRowVector> lhs,
              pixel_vector<ChannelValue2, Layout, IsRowVector> rhs)
    {
        lhs -= rhs;
        return lhs;
    }
    
    template <typename ChannelValue1, typename ChannelValue2, typename Layout, bool IsRowVector>
    inline pixel_vector<std::common_type_t<ChannelValue1, ChannelValue2>, Layout>
    operator*(pixel_vector<ChannelValue1, Layout, IsRowVector> lhs,
              pixel_vector<ChannelValue2, Layout, IsRowVector> rhs)
    {
        lhs *= rhs;
        return lhs;
    }
    
    template <typename ChannelValue1, typename ChannelValue2, typename Layout, bool IsRowVector>
    inline pixel_vector<std::common_type_t<ChannelValue1, ChannelValue2>, Layout>
    operator/(pixel_vector<ChannelValue1, Layout, IsRowVector> lhs,
              pixel_vector<ChannelValue2, Layout, IsRowVector> rhs)
    {
        lhs /= rhs;
        return lhs;
    }
    
    template <typename Pixel, bool IsRowVector = blaze::rowVector>
    struct pixel_vector_type;
    
    template <typename ChannelValue, typename Layout, bool IsRowVector>
    struct pixel_vector_type<boost::gil::pixel<ChannelValue, Layout>, IsRowVector> {
        using type = pixel_vector<ChannelValue, Layout, IsRowVector>;
    };
    
    namespace blaze
    {
    template <typename ChannelValue, typename Layout, bool IsRowVector>
    struct UnderlyingElement<pixel_vector<ChannelValue, Layout, IsRowVector>> {
        using Type = ChannelValue;
    };
    
    template <typename ChannelValue, typename Layout, bool IsRowVector>
    struct IsDenseVector<pixel_vector<ChannelValue, Layout, IsRowVector>> : std::true_type {
    };
    } // namespace blaze
    

    I guess there is no documentation for my case as hand-rolling a vector is not considered yet. Would it be hard to make a full blown StaticVector out of this? Like with expression templates and stuff.

  4. Klaus Iglberger

    Hi Olzhas!

    It would take some more effort to make this work in all relevant operations. That’s why we don’t plan for users of Blaze to do this on their own and why there is no documentation for this. However, if it works for you then of course this is a reasonable solution.

    It should not be necessary to provide the two template specializations for UnderlyingElement and IsDenseVector. Did you try it without them? Also, did you try to use a gil::pixel as a custom data type for a CustomMatrix? If yes, why and how did it fail?

    I apologize for the late reply,

    Best regards,

    Klaus!

  5. Log in to comment