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

Razakhel / RaZ / 9140786487

18 May 2024 03:12PM UTC coverage: 79.221% (-0.1%) from 79.343%
9140786487

push

github

Razakhel
[Data/ImageFormat] Image export is handled by stb

- Removed extern/glfw/deps, as it was unused and had a conflicting stb_image_write.h much older than the new one

42 of 50 new or added lines in 1 file covered. (84.0%)

15 existing lines in 1 file now uncovered.

8018 of 10121 relevant lines covered (79.22%)

2068.98 hits per line

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

84.4
/src/RaZ/Data/ImageFormat.cpp
1
#include "RaZ/Data/Image.hpp"
2
#include "RaZ/Data/ImageFormat.hpp"
3
#include "RaZ/Utils/FilePath.hpp"
4
#include "RaZ/Utils/Logger.hpp"
5
#include "RaZ/Utils/StrUtils.hpp"
6

7
#define STB_IMAGE_IMPLEMENTATION
8
#define STBI_FAILURE_USERMSG
9
#define STBI_WINDOWS_UTF8
10
#include "stb_image.h"
11

12
#define STB_IMAGE_WRITE_IMPLEMENTATION
13
#define STBIW_WINDOWS_UTF8
14
#include "stb_image_write.h"
15

16
#include "tracy/Tracy.hpp"
17

18
namespace Raz::ImageFormat {
19

20
namespace {
21

22
enum class FileFormat {
23
  UNKNOWN,
24
  BMP,
25
  PNG,
26
  JPG,
27
  TGA,
28
  HDR
29
};
30

31
struct ImageDataDeleter {
32
  void operator()(void* data) noexcept { stbi_image_free(data); }
102✔
33
};
34

35
ImageColorspace recoverColorspace(int channelCount) {
102✔
36
  switch (channelCount) {
102✔
37
    case 1: return ImageColorspace::GRAY;
17✔
38
    case 2: return ImageColorspace::GRAY_ALPHA;
×
39
    case 3: return ImageColorspace::RGB;
63✔
40
    case 4: return ImageColorspace::RGBA;
22✔
41
    default:
×
42
      throw std::invalid_argument("Error: Unsupported number of channels.");
×
43
  }
44
}
45

46
FileFormat recoverFileFormat(const std::string& fileExt) {
23✔
47
  if (fileExt == "bmp")
23✔
48
    return FileFormat::BMP;
2✔
49
  if (fileExt == "hdr")
21✔
50
    return FileFormat::HDR;
1✔
51
  if (fileExt == "jpg" || fileExt == "jpeg")
20✔
52
    return FileFormat::JPG;
3✔
53
  if (fileExt == "png")
17✔
54
    return FileFormat::PNG;
15✔
55
  if (fileExt == "tga")
2✔
56
    return FileFormat::TGA;
2✔
57

NEW
58
  return FileFormat::UNKNOWN;
×
59
}
60

61
Image createImageFromData(int width, int height, int channelCount, bool isHdr, const std::unique_ptr<void, ImageDataDeleter>& data) {
102✔
62
  const std::size_t valueCount = width * height * channelCount;
102✔
63

64
  Image img(width, height, recoverColorspace(channelCount), (isHdr ? ImageDataType::FLOAT : ImageDataType::BYTE));
102✔
65

66
  if (isHdr)
102✔
67
    std::copy_n(static_cast<float*>(data.get()), valueCount, static_cast<float*>(img.getDataPtr()));
×
68
  else
69
    std::copy_n(static_cast<uint8_t*>(data.get()), valueCount, static_cast<uint8_t*>(img.getDataPtr()));
102✔
70

71
  return img;
102✔
72
}
×
73

74
} // namespace
75

76
Image load(const FilePath& filePath, bool flipVertically) {
86✔
77
  ZoneScopedN("ImageFormat::load");
78

79
  Logger::debug("[ImageFormat] Loading image '" + filePath + "'...");
86✔
80

81
  const std::string fileStr = filePath.toUtf8();
86✔
82
  const bool isHdr = (stbi_is_hdr(fileStr.c_str()) != 0);
86✔
83

84
  stbi_set_flip_vertically_on_load(flipVertically);
86✔
85

86
  int width {};
86✔
87
  int height {};
86✔
88
  int channelCount {};
86✔
89
  std::unique_ptr<void, ImageDataDeleter> data;
86✔
90

91
  if (isHdr)
86✔
92
    data.reset(stbi_loadf(fileStr.c_str(), &width, &height, &channelCount, 0));
×
93
  else
94
    data.reset(stbi_load(fileStr.c_str(), &width, &height, &channelCount, 0));
86✔
95

96
  if (data == nullptr)
86✔
97
    throw std::invalid_argument("[ImageFormat] Cannot load image '" + filePath + "': " + stbi_failure_reason());
×
98

99
  Image img = createImageFromData(width, height, channelCount, isHdr, data);
86✔
100

101
  Logger::debug("[ImageFormat] Loaded image");
86✔
102

103
  return img;
172✔
104
}
86✔
105

106
Image loadFromData(const std::vector<unsigned char>& imgData, bool flipVertically) {
15✔
107
  return loadFromData(imgData.data(), imgData.size(), flipVertically);
15✔
108
}
109

110
Image loadFromData(const unsigned char* imgData, std::size_t dataSize, bool flipVertically) {
16✔
111
  ZoneScopedN("ImageFormat::loadFromData");
112

113
  Logger::debug("[ImageFormat] Loading image from data...");
16✔
114

115
  stbi_set_flip_vertically_on_load(flipVertically);
16✔
116

117
  const bool isHdr = (stbi_is_hdr_from_memory(imgData, static_cast<int>(dataSize)) != 0);
16✔
118

119
  int width {};
16✔
120
  int height {};
16✔
121
  int channelCount {};
16✔
122
  std::unique_ptr<void, ImageDataDeleter> data;
16✔
123

124
  if (isHdr)
16✔
125
    data.reset(stbi_loadf_from_memory(imgData, static_cast<int>(dataSize), &width, &height, &channelCount, 0));
×
126
  else
127
    data.reset(stbi_load_from_memory(imgData, static_cast<int>(dataSize), &width, &height, &channelCount, 0));
16✔
128

129
  if (data == nullptr)
16✔
130
    throw std::invalid_argument("[ImageFormat] Cannot load image from data: " + std::string(stbi_failure_reason()));
×
131

132
  Image img = createImageFromData(width, height, static_cast<uint8_t>(channelCount), isHdr, data);
16✔
133

134
  Logger::debug("[ImageFormat] Loaded image from data");
16✔
135

136
  return img;
32✔
137
}
16✔
138

139
void save(const FilePath& filePath, const Image& image, bool flipVertically) {
23✔
140
  ZoneScopedN("ImageFormat::save");
141

142
  Logger::debug("[ImageFormat] Saving image to '" + filePath + "'...");
23✔
143

144
  const std::string fileStr = filePath.toUtf8();
23✔
145
  const std::string fileExt = StrUtils::toLowercaseCopy(filePath.recoverExtension().toUtf8());
46✔
146

147
  stbi_flip_vertically_on_write(flipVertically);
23✔
148

149
  const auto imgWidth        = static_cast<int>(image.getWidth());
23✔
150
  const auto imgHeight       = static_cast<int>(image.getHeight());
23✔
151
  const auto imgChannelCount = static_cast<int>(image.getChannelCount());
23✔
152

153
  const FileFormat format = recoverFileFormat(fileExt);
23✔
154

155
  switch (format) {
23✔
156
    case FileFormat::BMP:
22✔
157
    case FileFormat::JPG:
158
    case FileFormat::PNG:
159
    case FileFormat::TGA:
160
      if (image.getDataType() != ImageDataType::BYTE)
22✔
NEW
161
        throw std::invalid_argument("[ImageFormat] Saving a non-HDR image requires a byte data type.");
×
162
      break;
22✔
163

164
    case FileFormat::HDR:
1✔
165
      if (image.getDataType() != ImageDataType::FLOAT)
1✔
NEW
166
        throw std::invalid_argument("[ImageFormat] Saving an HDR image requires a floating-point data type.");
×
167
      break;
1✔
168

NEW
169
    case FileFormat::UNKNOWN:
×
170
    default:
NEW
171
      break;
×
172
  }
173

174
  int result {};
23✔
175

176
  switch (format) {
23✔
177
    case FileFormat::BMP:
2✔
178
      result = stbi_write_bmp(fileStr.c_str(), imgWidth, imgHeight, imgChannelCount, image.getDataPtr());
2✔
179
      break;
2✔
180

181
    case FileFormat::HDR:
1✔
182
      result = stbi_write_hdr(fileStr.c_str(), imgWidth, imgHeight, imgChannelCount, static_cast<const float*>(image.getDataPtr()));
1✔
183
      break;
1✔
184

185
    case FileFormat::JPG:
3✔
186
      result = stbi_write_jpg(fileStr.c_str(), imgWidth, imgHeight, imgChannelCount, image.getDataPtr(), 90);
3✔
187
      break;
3✔
188

189
    case FileFormat::PNG:
15✔
190
      result = stbi_write_png(fileStr.c_str(), imgWidth, imgHeight, imgChannelCount, image.getDataPtr(), imgWidth * imgChannelCount);
15✔
191
      break;
15✔
192

193
    case FileFormat::TGA:
2✔
194
      result = stbi_write_tga(fileStr.c_str(), imgWidth, imgHeight, imgChannelCount, image.getDataPtr());
2✔
195
      break;
2✔
196

NEW
197
    case FileFormat::UNKNOWN:
×
198
    default:
NEW
199
      throw std::invalid_argument("[ImageFormat] Unsupported image file extension '" + fileExt + "' for saving.");
×
200
  }
201

202
  if (result == 0)
23✔
NEW
203
    throw std::invalid_argument("[ImageFormat] Failed to save image.");
×
204

205
  Logger::debug("[ImageFormat] Saved image");
23✔
206
}
23✔
207

208
} // namespace Raz::ImageFormat
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