• 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

61.34
/src/RaZ/Data/PngFormat.cpp
1
#include "RaZ/Data/Image.hpp"
2
#include "RaZ/Data/PngFormat.hpp"
3
#include "RaZ/Utils/FilePath.hpp"
4
#include "RaZ/Utils/Logger.hpp"
5

6
#include "png.h"
7
#include "zlib.h"
8

9
#include "tracy/Tracy.hpp"
10

11
#include <array>
12
#include <fstream>
13

14
namespace Raz::PngFormat {
15

16
namespace {
17

18
constexpr uint8_t PNG_HEADER_SIZE = 8;
19

20
inline bool validatePng(std::istream& file) {
6✔
21
  std::array<png_byte, PNG_HEADER_SIZE> header {};
6✔
22
  file.read(reinterpret_cast<char*>(header.data()), PNG_HEADER_SIZE);
6✔
23

24
  return (png_sig_cmp(header.data(), 0, PNG_HEADER_SIZE) == 0);
6✔
25
}
26

27
} // namespace
28

29
Image load(const FilePath& filePath, bool flipVertically) {
6✔
30
  ZoneScopedN("PngFormat::load");
31

32
  std::ifstream file(filePath, std::ios_base::in | std::ios_base::binary);
6✔
33

34
  if (!file)
6✔
35
    throw std::invalid_argument("Error: Could not open the PNG file '" + filePath + "'");
×
36

37
  if (!validatePng(file))
6✔
38
    throw std::runtime_error("Error: Not a valid PNG file");
×
39

40
  png_structp readStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
6✔
41
  if (readStruct == nullptr)
6✔
42
    throw std::runtime_error("Error: Could not initialize PNG read struct");
×
43

44
  png_infop infoStruct = png_create_info_struct(readStruct);
6✔
45
  if (infoStruct == nullptr)
6✔
46
    throw std::runtime_error("Error: Could not initialize PNG info struct");
×
47

48
  png_set_read_fn(readStruct, &file, [] (png_structp pngReadPtr, png_bytep data, png_size_t length) {
6✔
49
    png_voidp inPtr = png_get_io_ptr(pngReadPtr);
93✔
50
    static_cast<std::istream*>(inPtr)->read(reinterpret_cast<char*>(data), static_cast<std::streamsize>(length));
93✔
51
  });
93✔
52

53
  // Setting the amount signature bytes we've already read
54
  png_set_sig_bytes(readStruct, PNG_HEADER_SIZE);
6✔
55

56
  png_read_info(readStruct, infoStruct);
6✔
57

58
  const unsigned int width  = png_get_image_width(readStruct, infoStruct);
6✔
59
  const unsigned int height = png_get_image_height(readStruct, infoStruct);
6✔
60
  const uint8_t colorType   = png_get_color_type(readStruct, infoStruct);
6✔
61
  uint8_t bitDepth          = png_get_bit_depth(readStruct, infoStruct);
6✔
62
  uint8_t channelCount      = png_get_channels(readStruct, infoStruct);
6✔
63

64
  ImageColorspace colorspace {};
6✔
65

66
  switch (colorType) {
6✔
67
    case PNG_COLOR_TYPE_GRAY:
×
68
      if (bitDepth < 8) {
×
69
        png_set_expand_gray_1_2_4_to_8(readStruct);
×
70
        bitDepth = 8;
×
71
      }
72

73
      colorspace = ImageColorspace::GRAY;
×
74
      break;
×
75

76
    case PNG_COLOR_TYPE_GRAY_ALPHA:
×
77
      colorspace = ImageColorspace::GRAY_ALPHA;
×
78
      break;
×
79

80
    case PNG_COLOR_TYPE_PALETTE:
×
81
      png_set_palette_to_rgb(readStruct);
×
82
      channelCount = 3;
×
83
      [[fallthrough]];
84

85
    case PNG_COLOR_TYPE_RGB:
×
86
    default:
87
      colorspace = ImageColorspace::RGB;
×
88
      break;
×
89

90
    case PNG_COLOR_TYPE_RGBA:
6✔
91
      colorspace = ImageColorspace::RGBA;
6✔
92
      break;
6✔
93
  }
94

95
  if (!png_set_interlace_handling(readStruct))
6✔
96
    Logger::error("[PngLoad] Could not set PNG interlace handling.");
×
97

98
  png_set_scale_16(readStruct);
6✔
99

100
  // Adding full alpha channel to the image if it possesses transparency
101
  if (png_get_valid(readStruct, infoStruct, PNG_INFO_tRNS)) {
6✔
102
    png_set_tRNS_to_alpha(readStruct);
×
103
    colorspace = ImageColorspace::RGBA;
×
104
    ++channelCount;
×
105
  }
106

107
  png_read_update_info(readStruct, infoStruct);
6✔
108

109
  Image image(width, height, colorspace, ImageDataType::BYTE);
6✔
110
  auto* imgData = static_cast<uint8_t*>(image.getDataPtr());
6✔
111

112
  std::vector<png_bytep> rowPtrs(height);
12✔
113

114
  // Mapping row's elements to data's
115
  for (std::size_t heightIndex = 0; heightIndex < height; ++heightIndex)
21✔
116
    rowPtrs[(flipVertically ? height - 1 - heightIndex : heightIndex)] = &imgData[width * channelCount * heightIndex];
15✔
117

118
  png_read_image(readStruct, rowPtrs.data());
6✔
119
  png_read_end(readStruct, infoStruct);
6✔
120
  png_destroy_read_struct(&readStruct, nullptr, &infoStruct);
6✔
121

122
  return image;
12✔
123
}
6✔
124

125
void save(const FilePath& filePath, const Image& image, bool flipVertically) {
3✔
126
  ZoneScopedN("PngFormat::save");
127

128
  if (image.isEmpty()) {
3✔
129
    Logger::error("[PngSave] Cannot save empty image to '" + filePath + "'.");
×
130
    return;
×
131
  }
132

133
  std::ofstream file(filePath, std::ios_base::out | std::ios_base::binary);
3✔
134

135
  if (!file)
3✔
136
    throw std::invalid_argument("Error: Unable to create a PNG file as '" + filePath + "'; path to file must exist");
×
137

138
  png_structp writeStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
3✔
139
  if (!writeStruct)
3✔
140
    throw std::runtime_error("Error: Could not initialize PNG write struct");
×
141

142
  png_infop infoStruct = png_create_info_struct(writeStruct);
3✔
143
  if (!infoStruct)
3✔
144
    throw std::runtime_error("Error: Could not initialize PNG info struct");
×
145

146
  int colorType {};
3✔
147

148
  switch (image.getColorspace()) {
3✔
UNCOV
149
    case ImageColorspace::GRAY:
×
UNCOV
150
      colorType = PNG_COLOR_TYPE_GRAY;
×
UNCOV
151
      break;
×
152

153
    case ImageColorspace::GRAY_ALPHA:
×
154
      colorType = PNG_COLOR_TYPE_GRAY_ALPHA;
×
155
      break;
×
156

UNCOV
157
    case ImageColorspace::RGB:
×
158
    case ImageColorspace::SRGB:
159
    default:
UNCOV
160
      colorType = PNG_COLOR_TYPE_RGB;
×
UNCOV
161
      break;
×
162

163
    case ImageColorspace::RGBA:
3✔
164
    case ImageColorspace::SRGBA:
165
      colorType = PNG_COLOR_TYPE_RGBA;
3✔
166
      break;
3✔
167
  }
168

169
  png_set_compression_level(writeStruct, 6);
3✔
170

171
  if (image.getChannelCount() >= 2) {
3✔
172
    png_set_compression_strategy(writeStruct, Z_FILTERED);
3✔
173
    png_set_filter(writeStruct, 0, PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_PAETH);
3✔
174
  } else {
UNCOV
175
    png_set_compression_strategy(writeStruct, Z_DEFAULT_STRATEGY);
×
176
  }
177

178
  png_set_IHDR(writeStruct,
3✔
179
               infoStruct,
180
               image.getWidth(),
181
               image.getHeight(),
182
               8, // Only 8-bit images will be written
183
               colorType,
184
               PNG_INTERLACE_NONE,
185
               PNG_COMPRESSION_TYPE_BASE,
186
               PNG_FILTER_TYPE_BASE);
187

188
  png_set_write_fn(writeStruct, &file, [] (png_structp pngWritePtr, png_bytep data, png_size_t length) {
3✔
189
    png_voidp outPtr = png_get_io_ptr(pngWritePtr);
27✔
190
    static_cast<std::ostream*>(outPtr)->write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(length));
27✔
191
  }, nullptr);
27✔
192
  png_write_info(writeStruct, infoStruct);
3✔
193

194
  const std::size_t pixelIndexBase = image.getWidth() * image.getChannelCount();
3✔
195

196
  if (image.getDataType() == ImageDataType::FLOAT) {
3✔
197
    // Manually converting floating-point pixels to standard byte ones
UNCOV
198
    const auto* pixels = static_cast<const float*>(image.getDataPtr());
×
UNCOV
199
    std::vector<uint8_t> rgbPixels(image.getWidth() * image.getHeight() * image.getChannelCount());
×
200

UNCOV
201
    for (std::size_t i = 0; i < rgbPixels.size(); ++i)
×
UNCOV
202
      rgbPixels[i] = static_cast<uint8_t>(pixels[i] * 255);
×
203

UNCOV
204
    const uint8_t* dataPtr = rgbPixels.data();
×
205

UNCOV
206
    for (std::size_t heightIndex = 0; heightIndex < image.getHeight(); ++heightIndex)
×
UNCOV
207
      png_write_row(writeStruct, &dataPtr[pixelIndexBase * (flipVertically ? image.getHeight() - 1 - heightIndex : heightIndex)]);
×
UNCOV
208
  } else {
×
209
    const auto* dataPtr = static_cast<const uint8_t*>(image.getDataPtr());
3✔
210

211
    for (std::size_t heightIndex = 0; heightIndex < image.getHeight(); ++heightIndex)
12✔
212
      png_write_row(writeStruct, &dataPtr[pixelIndexBase * (flipVertically ? image.getHeight() - 1 - heightIndex : heightIndex)]);
9✔
213
  }
214

215
  png_write_end(writeStruct, infoStruct);
3✔
216
  png_destroy_write_struct(&writeStruct, &infoStruct);
3✔
217
}
3✔
218

219
} // namespace Raz::PngFormat
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