I was tired to always perform pixel manipulation one by one, so I started to create my own templated header to perform computation over pixel (RGB, or RGBA), but I also make it for ND for some cases.
There are few comments in the code for some features that Im not sure yet.
I also develop it as std::array to have operator overloading, despite the classic C array could have been better for my pipeline (data casting mostly).
Usually those methods are used over int or float and occasionally uchar. As you can see, I didn't handled the limits, or the negative. Im still questioning myself to create dedicated template for uchar.
And a last question I have, I never really timed it, or check compiler behavior, but should it be better to use dedicated function for 3D and 4D ?
#ifndef PX_ND_STD_ARRAY_UTILITY_H
#define PX_ND_STD_ARRAY_UTILITY_H
// =============================================================================
// We should prefer using std::array, since we are able to define operator
// function on it. This lead to a better usage instead using C Array.
//
// using pxNDArr = std::array<T, N>;
// vs
// using pxNDArr = T[N];
//
// =============================================================================
#include <iostream>
#include <array>
// =============================================================================
// Define of type alias for templated std::array. (ND, 3D and 4D).
// =============================================================================
template <class T, std::size_t N>
using pxNDArr = std::array<T, N>;
// Special class for color pixel RGB.
template <class T>
using px3DArr = pxNDArr<T, 3>;
// Special class for color pixel RGBA.
template <class T>
using px4DArr = pxNDArr<T, 4>;
// =============================================================================
// Define of type alias for a NxN template std::array (No operator define on it).
// =============================================================================
template <class T, std::size_t N1, std::size_t N2>
using pxMatrixNM = std::array<std::array<T, N2>, N1>;
// This template allows to check a "typename" condition.
template <typename Condition>
using EnableIf = typename std::enable_if<Condition::value>::type;
// =============================================================================
// Define alias to ensure to use the right Input, Output, or InOut type, same for returns.
// =============================================================================
template<class T, std::size_t N>
using InArg = const pxNDArr<T, N>&;
template<class T, std::size_t N>
using OutArg = pxNDArr<T, N>&;
template<class T, std::size_t N>
using InOutArg = OutArg<T, N>;
template<class T, std::size_t N>
using OutRet = pxNDArr<T, N>;
// Constant<T> is longer than Constant<T>, but is safer to use to avoid to
// have constant param non-const ?
// NOTE: Not used yet, wondering if I should.
template<class T = EnableIf<std::is_arithmetic<T>>>
using Constant = const T;
// =============================================================================
// Special methods that apply pixel manipulation.
// =============================================================================
// Apply a mathematical cross product between vector arr1 and arr2. Output the result into dst.
template<class T>
void crossProduct(const px3DArr<T>& arr1, const px3DArr<T>& arr2, px3DArr<T>& dst)
{
dst[0] = arr1[1] * arr2[2] - arr1[2] * arr2[1];
dst[1] = arr1[2] * arr2[0] - arr1[0] * arr2[2];
dst[2] = arr1[0] * arr2[1] - arr1[1] * arr2[0];
}
template<class T>
px3DArr<T> crossProduct(const px3DArr<T>& arr1, const px3DArr<T>& arr2)
{
px3DArr<T> dst;
crossProduct(arr1, arr2, dst);
return dst;
}
// Swap color channel: RGB to BGR and vice-versa.
template<class T>
void swapChannel(const px3DArr<T>& arr, px3DArr<T>& dst)
{
dst[0] = arr[2];
dst[1] = arr[1];
dst[2] = arr[0];
}
template<class T>
px3DArr<T> swapChannel(const px3DArr<T>& arr)
{
px3DArr<T> dst;
swapChannel(arr, dst);
return dst;
}
template<class T>
void swapChannelInPlace(px3DArr<T>& arr)
{
T tmp = arr[0];
arr[0] = arr[2];
arr[2] = tmp;
}
// =============================================================================
// Method for all types of arrays (ND, 3D, or 4D)
// =============================================================================
// Copy an array into another one.
template<class T, std::size_t N>
void copy(InArg<T, N> arr, OutArg<T, N> dst)
{
std::copy_n(arr.begin(), N, dst.begin());
}
template<class T, std::size_t N>
OutRet<T, N> copy(InArg<T, N> arr)
{
pxNDArr<T, N> dst;
copy(arr, dst);
return dst;
}
// This method clamp the value InArg between a min and a max range limit.
template<class T, std::size_t N>
void clamp(InArg<T, N> arr, OutArg<T, N> dst, Constant<T> minVal, Constant<T> maxVal)
{
for ( int i = 0; i < N; i++ )
dst[i] = ( ( arr[i] < minVal ) ? minVal : ( ( arr[i] > maxVal ) ? maxVal : ( arr[i] ) ) );
}
template<class T, std::size_t N>
OutRet<T, N> clamp(InArg<T, N> arr, Constant<T> minVal, Constant<T> maxVal)
{
pxNDArr<T, N> dst;
clamp(arr, dst, minVal, maxVal);
return dst;
}
// Addition operator between an std::array and a constant of the same type.
template <class T, std::size_t N>
OutRet<T, N> operator+( InArg<T, N> arr, Constant<T> constant )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr[i] + constant;
return dst;
}
// Addition operator between an std::array and another array (same type)
template <class T, std::size_t N>
OutRet<T, N> operator+( InArg<T, N> arr1, InArg<T, N> arr2 )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr1[i] + arr2[i];
return dst;
}
// substraction operator between an std::array and a constant (same type)
template <class T, std::size_t N>
OutRet<T, N> operator-( InArg<T, N> arr, Constant<T> constant )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr[i] - constant;
return dst;
}
// substraction operator between an std::array and another array (same type)
template <class T, std::size_t N>
OutRet<T, N> operator-( InArg<T, N> arr1, InArg<T, N> arr2 )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr1[i] - arr2[i];
return dst;
}
// Multiplication operator between an std::array and a constant (same type)
template <class T, std::size_t N>
OutRet<T, N> operator*( InArg<T, N> arr, Constant<T> constant )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr[i] * constant;
return dst;
}
// Multiplication operator between an std::array and another array (same type)
template <class T, std::size_t N>
OutRet<T, N> operator*( InArg<T, N> arr1, InArg<T, N> arr2 )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr1[i] * arr2[i];
return dst;
}
// Divide operator between an std::array and a constant (same type)
template <class T, std::size_t N>
OutRet<T, N> operator/( InArg<T, N> arr, Constant<T> constant )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr[i] / constant;
return dst;
}
// Divide operator between an std::array and another array (same type)
template <class T, std::size_t N>
OutRet<T, N> operator/( InArg<T, N> arr1, InArg<T, N> arr2 )
{
pxNDArr<T, N> dst;
for ( int i = 0; i < N; i++ )
dst[i] = arr1[i] / arr2[i];
return dst;
}
// This addition two vector elements by elements.
template<class T, std::size_t N>
void add(InArg<T, N> arr1, InArg<T, N> arr2, OutArg<T, N> dst)
{
dst = arr1 + arr2;
}
// This subtract two vector elements by elements.
template<class T, std::size_t N>
void subtract(InArg<T, N> arr1, InArg<T, N> arr2, OutArg<T, N> dst)
{
dst = arr1 - arr2;
}
// This multiply two vector elements by elements.
template<class T, std::size_t N>
void multiply(InArg<T, N> arr1, InArg<T, N> arr2, OutArg<T, N> dst)
{
dst = arr1 * arr2;
}
// This divide two vector elements by elements.
template<class T, std::size_t N>
void divide(InArg<T, N> arr1, InArg<T, N> arr2, OutArg<T, N> dst)
{
dst = arr1 / arr2;
}
// This calculates the sum of a vector. (Warning with uchar)
template<class T, std::size_t N>
T sum(InArg<T, N> arr)
{
T sum = arr[0];
for ( int i = 1; i < N; i++ )
sum += arr[i];
return sum;
}
// This applies a dot product calculation between any type and any size vector.
template<class T, std::size_t N>
T dotProduct(InArg<T, N> arr1, InArg<T, N> arr2)
{
return sum(arr1 * arr2);
}
// This applies an exponent power over vector A and store the result into B.
template<class T, std::size_t N>
void pow(InArg<T, N> arr, OutArg<T, N> dst, Constant<T> exponent)
{
for ( int i = 0; i < N; i++ )
dst[i] = std::pow(arr[i], exponent);
}
// This applies an InArg place exponent power over vector A.
template<class T, std::size_t N>
void powInplace(InOutArg<T, N> arr, Constant<T> exponent)
{
for ( auto& it : arr )
it = std::pow(it, exponent);
}
// This applies an exponent power over vector A and return result into a new array.
template<class T, std::size_t N>
OutRet<T, N> pow(InArg<T, N> arr, Constant<T> exponent)
{
pxNDArr<T, N> dst;
pow(arr, dst, exponent);
return dst;
}
// This applies a sqrt element by element.
template<class T, std::size_t N>
void sqrt(InArg<T, N> arr, OutArg<T, N> dst)
{
for ( int i = 0; i < N; i++ )
dst[i] = std::sqrt(arr[i]);
}
// This applies an InArg place sqrt element by element.
template<class T, std::size_t N>
void sqrtInplace(InOutArg<T, N>& arr)
{
for ( auto& it : arr )
it = std::sqrt(it);
}
template<class T, std::size_t N>
OutRet<T, N> sqrt(InArg<T, N> arr)
{
pxNDArr<T, N> dst;
sqrt(arr, dst);
return dst;
}
// This return the length of any vector.
// NOTE: The length is always float.
template<class T, std::size_t N>
const T vectorLength(InArg<T, N> arr)
{
return static_cast<T>(std::sqrt(static_cast<float>( dotProduct(arr, arr) )));
}
// Compute the distance between vector A and B.
// NOTE: The distance is always float.
template<class T, std::size_t N>
const float distanceBetweenVector(InArg<T, N> arr1, InArg<T, N> arr2)
{
return vectorLength(arr1 - arr2);
}
// TODO: Should we limit T to be float ? Since vectorLength only returns float.
// InArg-place vector normalization.
template<class T, std::size_t N>
void normalizeVectorInplace(InOutArg<T, N>& arr)
{
arr /= vectorLength(arr);
}
// TODO: Should we limit T to be float ? Since vectorLength only returns float.
// Normalized vector A and output the result into dst vector (means not InArg-place).
template<class T, std::size_t N>
void normalizeVector(InArg<T, N> arr, OutArg<T, N> dst)
{
const float len = vectorLength(arr);
dst = arr / static_cast<T>(len);
}
// Normalized vector A and output the result into a new array
template<class T, std::size_t N>
OutRet<T, N> normalizeVector(InArg<T, N> arr)
{
OutRet<T, N> dst;
normalizeVector(arr, dst);
return dst;
}
// This function allows to convert from type T1 to type T2 a vector.
// Might be useful when you want to use sum of any other process on a
// unsigned char vector.
template<class T1, class T2, std::size_t N>
void convert(InArg<T1, N> arr, InArg<T2, N> dst)
{
for ( int i = 0; i < N; i++ )
dst[i] = static_cast<T2>( arr[i] );
}
template<class T1, class T2, std::size_t N>
OutRet<T2, N> convert(InArg<T1, N> arr)
{
pxNDArr<T2, N> dst;
convert(arr, dst);
return dst;
}
// This function prints the vector values.
template<class T, std::size_t N>
void printVector(InArg<T, N> arr)
{
for ( auto& it : arr )
std::cout << it << " ";
std::cout << std::endl << std::endl;
}
// TODO: Implement rounding methods, etc.
#endif // PX_ND_STD_ARRAY_UTILITY_H
Here few unit tests, really simple.
#include "pxNDstdArray.h"
#include <cassert>
#include <string>
#define EPS 1e-6
template<class T>
void assertArr(const px3DArr<T>& arr, const T v1, const T v2, const T v3, const std::string& name)
{
do
{
if ( arr[0] - v1 > EPS ) break;
if ( arr[1] - v2 > EPS ) break;
if ( arr[2] - v3 > EPS ) break;
return;
} while ( false );
std::cout << "Assertion failed for macros: " << name << std::endl;
std::cout << arr[0] << " " << arr[1] << " " << arr[2] << " != " << v1 << " " << v2 << " " << v3 << std::endl;
//exit(-1);
}
template<class T>
void assertVal(const T v1, const T v2, const std::string& name)
{
if ( v1 - v2 < EPS ) return;
std::cout << "Assertion failed for macros: " << name << std::endl;
std::cout << v1 << " != " << v2 << std::endl;
}
int main()
{
// Initialization.
px3DArr<float> a = { 1.0f, 2.0f, 3.0f };
assertArr(a, 1.0f, 2.0f, 3.0f, "Initialization: A");
px3DArr<float> b = { 1.0f, 2.0f, 1.0f };
assertArr(b, 1.0f, 2.0f, 1.0f, "Initialization: B");
// Copy
assertArr(copy(a), 1.0f, 2.0f, 3.0f, "Copy: A");
// Clamp
assertArr(clamp(a, 0.0f, 2.0f), 1.0f, 2.0f, 2.0f, "Clamp: A between [0,2]");
// Overloaded operator
assertArr(a + b, 2.0f, 4.0f, 4.0f, "Add: A + B");
assertArr(a - b, 0.0f, 0.0f, 2.0f, "Sub: A - B");
assertArr(a * b, 1.0f, 4.0f, 3.0f, "Mult: A * B");
assertArr(a / b, 1.0f, 1.0f, 3.0f, "Div: A / B");
// Sum
assertVal(sum(b), 4.0f, "Sum: B");
// Dot product
assertVal(dotProduct(a, b), 8.0f, "Dot product A B");
// Pow
assertArr(pow(a, 2.0f), 1.0f, 4.0f, 9.0f, "Pow: a^2");
// sqrt
assertArr(sqrt(b), 1.0f, sqrt(2.0f), 1.0f, "Sqrt: B");
// Vector length
assertVal(vectorLength(a), sqrt(14.0f), "Length: A");
// Distance
assertVal(distanceBetweenVector(a, b), 2.0f, "Distance between A and B");
// Normalization
assertArr(normalizeVector(a), 0.267261f, 0.534522f, 0.801784f, "Normalize: A");
// Convert... Failing. I will check more on taht.
//px3DArr<int> c;
//convert<float,int>(a, c);
}