Skip to content

Commit

Permalink
[Data/Image] Added Image::recover/setPixel()
Browse files Browse the repository at this point in the history
- Those allow giving & recovering either single-value pixels, or multiple values at once with vectors
  - The size of those vectors must correspond to the image's channel count
  • Loading branch information
Razakhel committed Sep 17, 2023
1 parent 349d5a2 commit 26d267f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 5 deletions.
47 changes: 44 additions & 3 deletions include/RaZ/Data/Image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ using ImageDataBPtr = std::unique_ptr<ImageDataB>;
struct ImageDataF;
using ImageDataFPtr = std::unique_ptr<ImageDataF>;

template <typename T, std::size_t Size>
class Vector;

enum class ImageColorspace {
GRAY = 0,
GRAY_ALPHA,
Expand Down Expand Up @@ -155,6 +158,23 @@ class Image {
/// \param channelIndex Channel index of the value to be fetched.
/// \return Value at the given location.
float recoverFloatValue(std::size_t widthIndex, std::size_t heightIndex, uint8_t channelIndex) const;
/// Gets a single-component pixel from the image.
/// \note This gets a single value, hence requires an image with a single channel.
/// \tparam T Type of the pixel to be fetched.
/// \param widthIndex Width index of the pixel to be fetched.
/// \param heightIndex Height index of the pixel to be fetched.
/// \return Pixel at the given location.
template <typename T>
T recoverPixel(std::size_t widthIndex, std::size_t heightIndex) const;
/// Gets a pixel from the image with multiple components.
/// \note The image requires having a channel count equal to the given value count.
/// \tparam T Type of the pixel to be fetched.
/// \tparam N Number of values to be fetched. Must be equal to the image's channel count.
/// \param widthIndex Width index of the pixel to be fetched.
/// \param heightIndex Height index of the pixel to be fetched.
/// \return Pixel at the given location.
template <typename T, std::size_t N>
Vector<T, N> recoverPixel(std::size_t widthIndex, std::size_t heightIndex) const;
/// Sets a byte value in the image.
/// \note The image must have a byte data type for this function to execute properly.
/// \param widthIndex Width index of the value to be set.
Expand All @@ -169,6 +189,22 @@ class Image {
/// \param channelIndex Channel index of the value to be set.
/// \param val Value to be set.
void setFloatValue(std::size_t widthIndex, std::size_t heightIndex, uint8_t channelIndex, float val);
/// Sets a pixel in the image.
/// \tparam T Type of the pixel to be set.
/// \param widthIndex Width index of the pixel to be set.
/// \param heightIndex Height index of the pixel to be set.
/// \param val Value to be set.
template <typename T>
void setPixel(std::size_t widthIndex, std::size_t heightIndex, T val);
/// Sets a pixel in the image with multiple components.
/// \note The image requires having a channel count equal to the given value count.
/// \tparam T Type of the pixel to be set.
/// \tparam N Number of values to be set. Must be equal to the image's channel count.
/// \param widthIndex Width index of the pixel to be set.
/// \param heightIndex Height index of the pixel to be set.
/// \param values Values to be set.
template <typename T, std::size_t N>
void setPixel(std::size_t widthIndex, std::size_t heightIndex, const Vector<T, N>& values);

Image& operator=(const Image& img);
Image& operator=(Image&&) noexcept = default;
Expand All @@ -184,12 +220,15 @@ class Image {
bool operator!=(const Image& img) const { return !(*this == img); }

private:
constexpr std::size_t computeIndex(std::size_t widthIndex, std::size_t heightIndex, uint8_t channelIndex) const noexcept {
constexpr std::size_t computeIndex(std::size_t widthIndex, std::size_t heightIndex) const noexcept {
assert("Error: The given width index is invalid." && widthIndex < m_width);
assert("Error: The given height index is invalid." && heightIndex < m_height);
assert("Error: The given channel index is invalid." && channelIndex < m_channelCount);
return heightIndex * m_width * m_channelCount + widthIndex * m_channelCount;
}

return heightIndex * m_width * m_channelCount + widthIndex * m_channelCount + channelIndex;
constexpr std::size_t computeIndex(std::size_t widthIndex, std::size_t heightIndex, uint8_t channelIndex) const noexcept {
assert("Error: The given channel index is invalid." && channelIndex < m_channelCount);
return computeIndex(widthIndex, heightIndex) + channelIndex;
}

template <typename T>
Expand All @@ -215,4 +254,6 @@ class Image {

} // namespace Raz

#include "RaZ/Data/Image.inl"

#endif // RAZ_IMAGE_HPP
54 changes: 54 additions & 0 deletions include/RaZ/Data/Image.inl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace Raz {

template <typename T>
T Image::recoverPixel(std::size_t widthIndex, std::size_t heightIndex) const {
assert("Error: Recovering a pixel of a single value requires an image having a single channel." && m_channelCount == 1);

if constexpr (std::is_same_v<T, uint8_t>)
return recoverByteValue(widthIndex, heightIndex, 0);
else if constexpr (std::is_same_v<T, float>)
return recoverFloatValue(widthIndex, heightIndex, 0);
else
static_assert(!std::is_same_v<T, T>, "Error: The given pixel's type to be recovered is unsupported.");
}

template <typename T, std::size_t N>
Vector<T, N> Image::recoverPixel(std::size_t widthIndex, std::size_t heightIndex) const {
static_assert(std::is_same_v<T, uint8_t> || std::is_same_v<T, float>, "Error: The given pixel's type to be recovered is unsupported.");
assert("Error: Recovering multiple values for a pixel requires an image having the same channel count." && m_channelCount == N);

const T* imgData = static_cast<T*>(m_data->getDataPtr()) + computeIndex(widthIndex, heightIndex);

Vector<T, N> res;

for (std::size_t i = 0; i < N; ++i)
res[i] = imgData[i];

return res;
}

template <typename T>
void Image::setPixel(std::size_t widthIndex, std::size_t heightIndex, T val) {
assert("Error: Recovering a pixel of a single value requires an image having a single channel." && m_channelCount == 1);

if constexpr (std::is_same_v<T, uint8_t>)
setByteValue(widthIndex, heightIndex, 0, val);
else if constexpr (std::is_same_v<T, float>)
setFloatValue(widthIndex, heightIndex, 0, val);
else
static_assert(!std::is_same_v<T, T>, "Error: The given pixel's type to be set is unsupported.");
}

template <typename T, std::size_t N>
void Image::setPixel(std::size_t widthIndex, std::size_t heightIndex, const Vector<T, N>& values) {
static_assert(std::is_same_v<T, uint8_t> || std::is_same_v<T, float>, "Error: The given pixel's type to be set is unsupported.");
assert("Error: Setting multiple values for a pixel requires an image having the same channel count." && m_channelCount == N);

T* imgData = static_cast<T*>(m_data->getDataPtr()) + computeIndex(widthIndex, heightIndex);

for (std::size_t i = 0; i < N; ++i)
imgData[i] = values[i];
}


} // namespace Raz
17 changes: 15 additions & 2 deletions tests/src/RaZ/Data/Image.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Catch.hpp"

#include "RaZ/Data/Image.hpp"
#include "RaZ/Math/Vector.hpp"

#include <numeric>

Expand Down Expand Up @@ -54,6 +55,9 @@ TEST_CASE("Image value access") {
CHECK(imgByte.recoverByteValue(0, 0, 0) == 1);
CHECK(imgByte.recoverByteValue(0, 0, 1) == 2);
CHECK(imgByte.recoverByteValue(0, 0, 2) == 3);

imgByte.setPixel(0, 0, Raz::Vec3b(4, 5, 6));
CHECK(imgByte.recoverPixel<uint8_t, 3>(0, 0) == Raz::Vec3b(4, 5, 6));
}

{
Expand Down Expand Up @@ -84,6 +88,15 @@ TEST_CASE("Image value access") {
CHECK(imgFloat.recoverFloatValue(0, 1, 1) == 6.f);
CHECK(imgFloat.recoverFloatValue(1, 1, 0) == 7.f);
CHECK(imgFloat.recoverFloatValue(1, 1, 1) == 8.f);

imgFloat.setPixel(0, 0, Raz::Vec2f(9.f, 10.f));
imgFloat.setPixel(0, 1, Raz::Vec2f(11.f, 12.f));
imgFloat.setPixel(1, 0, Raz::Vec2f(13.f, 14.f));
imgFloat.setPixel(1, 1, Raz::Vec2f(15.f, 16.f));
CHECK(imgFloat.recoverPixel<float, 2>(0, 0) == Raz::Vec2f(9.f, 10.f));
CHECK(imgFloat.recoverPixel<float, 2>(0, 1) == Raz::Vec2f(11.f, 12.f));
CHECK(imgFloat.recoverPixel<float, 2>(1, 0) == Raz::Vec2f(13.f, 14.f));
CHECK(imgFloat.recoverPixel<float, 2>(1, 1) == Raz::Vec2f(15.f, 16.f));
}
}

Expand Down Expand Up @@ -117,7 +130,7 @@ TEST_CASE("Image equality") {
Raz::Image imgB2Copy = imgB2;
CHECK(imgB2Copy == imgB2);

imgB2Copy.setByteValue(0, 0, 0, 1);
imgB2Copy.setPixel(0, 0, static_cast<uint8_t>(1));

CHECK(imgB2Copy != imgB2); // Same dimensions, same data type, but different content
CHECK_THAT(imgB2Copy, IsNearlyEqualToImage(imgB2)); // The near-equality check has a tolerance high enough to deem them nearly equal
Expand All @@ -127,7 +140,7 @@ TEST_CASE("Image equality") {
Raz::Image imgF2Copy = imgF2;
CHECK(imgF2Copy == imgF2);

imgF2Copy.setFloatValue(0, 0, 0, 0.01f);
imgF2Copy.setPixel(0, 0, 0.01f);

CHECK(imgF2Copy != imgF2); // Same as above, their content are not strictly equal
CHECK_THAT(imgF2Copy, IsNearlyEqualToImage(imgF2)); // But they are nearly equal to each other
Expand Down

0 comments on commit 26d267f

Please sign in to comment.