• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Razakhel / RaZ / 24038599832

06 Apr 2026 03:44PM UTC coverage: 74.524% (+0.05%) from 74.478%
24038599832

push

github

Razakhel
[Data/ImageUtils] Added equirectangular to cubemap conversion

56 of 68 new or added lines in 1 file covered. (82.35%)

8691 of 11662 relevant lines covered (74.52%)

1811.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/src/RaZ/Data/ImageUtils.cpp
1
#include "RaZ/Data/ImageUtils.hpp"
2
#include "RaZ/Math/Vector.hpp"
3
#include "RaZ/Utils/Threading.hpp"
4

5
#include "tracy/Tracy.hpp"
6

7
#include <algorithm>
8
#include <cmath>
9
#include <numbers>
10
#include <stdexcept>
11

12
namespace Raz {
13

14
std::array<Image, 6> ImageUtils::convertEquirectangularToCubemap(const Image& equirectangularImg) {
1✔
15
  ZoneScopedN("ImageUtils::convertEquirectangularToCubemap");
16

17
  if (equirectangularImg.isEmpty())
1✔
NEW
18
    throw std::invalid_argument("[ImageUtils] Empty equirectangular image given");
×
19

20
  const unsigned int faceSize  = equirectangularImg.getHeight() / 2;
1✔
21
  const float invFaceSize      = 1.f / static_cast<float>(faceSize);
1✔
22
  const auto srcWidth          = static_cast<int>(equirectangularImg.getWidth());
1✔
23
  const auto srcHeight         = static_cast<int>(equirectangularImg.getHeight());
1✔
24
  const ImageDataType dataType = equirectangularImg.getDataType();
1✔
25
  const uint8_t channelCount   = equirectangularImg.getChannelCount();
1✔
26

27
  std::array<Image, 6> faces;
1✔
28

29
  for (unsigned int faceIndex = 0; faceIndex < 6; ++faceIndex) {
7✔
30
    Image& faceImg = faces[faceIndex];
6✔
31
    faceImg        = Image(faceSize, faceSize, equirectangularImg.getColorspace(), dataType);
6✔
32

33
    Threading::parallelize(0, faceSize, [&faceImg, &equirectangularImg, faceSize, invFaceSize, faceIndex, srcWidth, srcHeight, channelCount, dataType] (const Threading::IndexRange& range) {
6✔
34
      ZoneScopedN("ImageUtils::convertEquirectangularToCubemap");
35

36
      for (std::size_t heightIndex = range.beginIndex; heightIndex < range.endIndex; ++heightIndex) {
216✔
37
        const float faceV = 2.f * (static_cast<float>(heightIndex) + 0.5f) * invFaceSize - 1.f;
192✔
38

39
        for (std::size_t widthIndex = 0; widthIndex < faceSize; ++widthIndex) {
6,336✔
40
          const float faceU = 2.f * (static_cast<float>(widthIndex) + 0.5f) * invFaceSize - 1.f;
6,144✔
41

42
          Vec3f direction;
6,144✔
43

44
          switch (faceIndex) {
6,144✔
45
            case 0: direction = Vec3f( 1.f,   -faceV, -faceU); break; // +X (right)
1,024✔
46
            case 1: direction = Vec3f(-1.f,   -faceV,  faceU); break; // -X (left)
1,024✔
47
            case 2: direction = Vec3f( faceU,  1.f,    faceV); break; // +Y (top)
1,024✔
48
            case 3: direction = Vec3f( faceU, -1.f,   -faceV); break; // -Y (bottom)
1,024✔
49
            case 4: direction = Vec3f( faceU, -faceV,  1.f);   break; // +Z (front)
1,024✔
50
            case 5: direction = Vec3f(-faceU, -faceV, -1.f);   break; // -Z (back)
1,024✔
NEW
51
            default: assert(false);
×
52
          }
53

54
          const float theta = std::atan2(direction.z(), direction.x()); // Longitude
6,144✔
55
          const float phi   = std::asin(direction.y() / direction.computeLength()); // Latitude
6,144✔
56

57
          const float srcPixelX = (theta / (2.f * std::numbers::pi_v<float>) + 0.5f) * static_cast<float>(srcWidth);
6,144✔
58
          const float srcPixelY = (0.5f - phi / std::numbers::pi_v<float>) * static_cast<float>(srcHeight);
6,144✔
59

60
          const auto srcBaseX = static_cast<int>(std::floor(srcPixelX - 0.5f));
6,144✔
61
          const auto srcBaseY = static_cast<int>(std::floor(srcPixelY - 0.5f));
6,144✔
62
          const float fracX   = srcPixelX - 0.5f - static_cast<float>(srcBaseX);
6,144✔
63
          const float fracY   = srcPixelY - 0.5f - static_cast<float>(srcBaseY);
6,144✔
64

65
          // Horizontal wrap (panorama is 360°), vertical clamp (poles)
66
          const auto wrapX = [srcWidth] (int x) {
12,288✔
67
            return static_cast<std::size_t>(((x % srcWidth) + srcWidth) % srcWidth);
12,288✔
68
          };
6,144✔
69
          const auto clampY = [srcHeight] (int y) {
12,288✔
70
            return static_cast<std::size_t>(std::clamp(y, 0, srcHeight - 1));
12,288✔
71
          };
6,144✔
72

73
          const std::size_t srcLeftIndex   = wrapX(srcBaseX);
6,144✔
74
          const std::size_t srcRightIndex  = wrapX(srcBaseX + 1);
6,144✔
75
          const std::size_t srcTopIndex    = clampY(srcBaseY);
6,144✔
76
          const std::size_t srcBottomIndex = clampY(srcBaseY + 1);
6,144✔
77

78
          for (uint8_t channelIndex = 0; channelIndex < channelCount; ++channelIndex) {
24,576✔
79
            if (dataType == ImageDataType::FLOAT) {
18,432✔
NEW
80
              const float topLeftVal     = equirectangularImg.recoverFloatValue(srcLeftIndex, srcTopIndex, channelIndex);
×
NEW
81
              const float topRightVal    = equirectangularImg.recoverFloatValue(srcRightIndex, srcTopIndex, channelIndex);
×
NEW
82
              const float bottomLeftVal  = equirectangularImg.recoverFloatValue(srcLeftIndex, srcBottomIndex, channelIndex);
×
NEW
83
              const float bottomRightVal = equirectangularImg.recoverFloatValue(srcRightIndex, srcBottomIndex, channelIndex);
×
NEW
84
              const float finalVal       = (1.f - fracX) * (1.f - fracY) * topLeftVal
×
NEW
85
                                         + fracX * (1.f - fracY) * topRightVal
×
NEW
86
                                         + (1.f - fracX) * fracY * bottomLeftVal
×
NEW
87
                                         + fracX * fracY * bottomRightVal;
×
NEW
88
              faceImg.setFloatValue(widthIndex, heightIndex, channelIndex, finalVal);
×
89
            } else {
90
              const auto topLeftVal     = static_cast<float>(equirectangularImg.recoverByteValue(srcLeftIndex, srcTopIndex, channelIndex));
18,432✔
91
              const auto topRightVal    = static_cast<float>(equirectangularImg.recoverByteValue(srcRightIndex, srcTopIndex, channelIndex));
18,432✔
92
              const auto bottomLeftVal  = static_cast<float>(equirectangularImg.recoverByteValue(srcLeftIndex, srcBottomIndex, channelIndex));
18,432✔
93
              const auto bottomRightVal = static_cast<float>(equirectangularImg.recoverByteValue(srcRightIndex, srcBottomIndex, channelIndex));
18,432✔
94
              const float finalVal      = (1.f - fracX) * (1.f - fracY) * topLeftVal
18,432✔
95
                                        + fracX * (1.f - fracY) * topRightVal
18,432✔
96
                                        + (1.f - fracX) * fracY * bottomLeftVal
18,432✔
97
                                        + fracX * fracY * bottomRightVal;
18,432✔
98
              faceImg.setByteValue(widthIndex, heightIndex, channelIndex, static_cast<uint8_t>(std::clamp(std::round(finalVal), 0.f, 255.f)));
18,432✔
99
            }
100
          }
101
        }
102
      }
103
    });
24✔
104
  }
105

106
  return faces;
1✔
NEW
107
}
×
108

109
} // namespace Raz
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc