Wildmeshing Toolkit
Image.cpp
Go to the documentation of this file.
1 #include "Image.hpp"
2 
3 #include <stb_image.h>
4 #include <stb_image_write.h>
5 #include <wmtk/utils/Logger.hpp>
6 #include "load_image_exr.hpp"
7 #include "save_image_exr.hpp"
8 
10 namespace {
11 float modulo(double x, double n)
12 {
13  float y = fmod(x, n);
14  if (y < 0) {
15  y += n;
16  }
17  return y;
18 }
19 
20 unsigned char double_to_unsignedchar(const double d)
21 {
22  return round(std::max(std::min(1., d), 0.) * 255);
23 }
24 } // namespace
25 
26 int Image::get_coordinate(const int x, const WrappingMode mode) const
27 {
28  auto size = std::max(width(), height());
29  assert(-size < x && x < 2 * size);
30  switch (mode) {
31  case WrappingMode::REPEAT: return (x + size) % size;
32 
34  if (x < 0)
35  return -(x % size);
36  else if (x < size)
37  return x;
38  else
39  return size - 1 - (x - size) % size;
40 
41  case WrappingMode::CLAMP_TO_EDGE: return std::clamp(x, 0, size - 1);
42  default: return (x + size) % size;
43  }
44 }
45 
46 std::pair<int, int> Image::get_pixel_index(const double& u, const double& v) const
47 {
48  int w = width();
49  int h = height();
50  auto size = std::max(w, h);
51  // x, y are between 0 and 1
52  auto x = u * size;
53  auto y = v * size;
54  const auto sx = static_cast<int>(std::floor(x - 0.5));
55  const auto sy = static_cast<int>(std::floor(y - 0.5));
56 
57  return {sx, sy};
58 }
59 
60 // set an image to have same value as the analytical function and save it to the file given
62  const std::function<float(const double&, const double&)>& f,
63  WrappingMode mode_x,
64  WrappingMode mode_y)
65 {
66  int h = height();
67  int w = width();
68 
69  m_image.resize(h, w);
70 
71  for (int i = 0; i < h; i++) {
72  for (int j = 0; j < w; j++) {
73  double u, v;
74  u = (static_cast<double>(j) + 0.5) / static_cast<double>(w);
75  v = (static_cast<double>(i) + 0.5) / static_cast<double>(h);
76  m_image(i, j) = f(u, v);
77  }
78  }
79  set_wrapping_mode(mode_x, mode_y);
80  return true;
81 }
82 
83 // save to hdr or exr
84 bool Image::save(const std::filesystem::path& path) const
85 {
86  wmtk::logger().trace("[save_image_hdr] start \"{}\"", path.string());
87  int w = width();
88  int h = height();
89  std::vector<float> buffer;
90  buffer.resize(w * h);
91 
92  for (auto i = 0; i < h; i++) {
93  for (auto j = 0; j < w; j++) {
94  buffer[i * w + j] = m_image(i, j);
95  }
96  }
97  if (path.extension() == ".hdr") {
98  auto res = stbi_write_hdr(path.string().c_str(), w, h, 1, buffer.data());
99  assert(res);
100  } else if (path.extension() == ".exr") {
101  auto res = save_image_exr_red_channel(w, h, buffer, path);
102  } else {
103  wmtk::logger().trace("[save_image_hdr] format doesn't support \"{}\"", path.string());
104  return false;
105  }
106 
107  wmtk::logger().trace("[save_image] done \"{}\"", path.string());
108 
109  return true;
110 }
111 
112 // load from hdr or exr
114  const std::filesystem::path& path,
115  const WrappingMode mode_x,
116  const WrappingMode mode_y)
117 {
118  int w, h, channels;
119  channels = 1;
120  std::vector<float> buffer;
121  if (path.extension() == ".exr") {
122  std::tie(w, h, buffer) = load_image_exr_red_channel(path);
123  assert(!buffer.empty());
124  } else if (path.extension() == ".hdr") {
125  auto res = stbi_loadf(path.string().c_str(), &w, &h, &channels, 1);
126  buffer.assign(res, res + w * h);
127  } else {
128  wmtk::logger().trace("[load_image] format doesn't support \"{}\"", path.string());
129  return;
130  }
131 
132  m_image.resize(w, h);
133 
134  for (int i = 0, k = 0; i < h; i++) {
135  for (int j = 0; j < w; j++) {
136  m_image(i, j) = buffer[k++];
137  }
138  }
139  m_image.colwise().reverseInPlace();
140  set_wrapping_mode(mode_x, mode_y);
141 }
142 
143 // down sample a image to size/2 by size/2
144 // used for mipmap construction
146 {
147  auto h = height();
148  auto w = width();
149  Image low_res_image(h / 2, w / 2);
150  for (int r = 0; r < h / 2; r++) {
151  for (int c = 0; c < w / 2; c++) {
152  low_res_image.set(
153  r,
154  c,
155  (m_image(r * 2, c * 2) + m_image(r * 2, c * 2 + 1) + m_image(r * 2 + 1, c * 2) +
156  m_image(r * 2 + 1, c * 2 + 1)) /
157  4.);
158  }
159  }
160  return low_res_image;
161 }
162 
163 std::array<Image, 3> combine_position_normal_texture(
164  double normalization_scale,
165  const Eigen::Matrix<double, 1, 3>& offset,
166  const std::filesystem::path& position_path,
167  const std::filesystem::path& normal_path,
168  const std::filesystem::path& height_path,
169  float min_height,
170  float max_height)
171 {
172  assert(std::filesystem::exists(position_path));
173  auto [w_p, h_p, index_red_p, index_green_p, index_blue_p, buffer_r_p, buffer_g_p, buffer_b_p] =
174  load_image_exr_split_3channels(position_path);
175 
176  auto buffer_size = buffer_r_p.size();
177  std::vector<float> buffer_r_d(buffer_size);
178  std::vector<float> buffer_g_d(buffer_size);
179  std::vector<float> buffer_b_d(buffer_size);
180 
181  if (std::filesystem::exists(normal_path) && std::filesystem::exists(height_path)) {
182  // Load normal + heightmap and compute displaced positions.
183  auto
184  [w_n,
185  h_n,
186  index_red_n,
187  index_green_n,
188  index_blue_n,
189  buffer_r_n,
190  buffer_g_n,
191  buffer_b_n] = load_image_exr_split_3channels(normal_path);
192  auto
193  [w_h,
194  h_h,
195  index_red_h,
196  index_green_h,
197  index_blue_h,
198  buffer_r_h,
199  buffer_g_h,
200  buffer_b_h] = load_image_exr_split_3channels(height_path);
201  assert(buffer_r_p.size() == buffer_r_n.size());
202  assert(buffer_r_p.size() == buffer_r_h.size());
203  assert(buffer_r_p.size() == buffer_g_p.size());
204  assert(buffer_r_p.size() == buffer_b_p.size());
205  auto scale = [&](float h) { return min_height * (1.f - h) + max_height * h; };
206  // displaced = positions * normalization_scale + heights * (2.0 * normals - 1.0) - offset
207  for (int i = 0; i < buffer_size; i++) {
208  buffer_r_d[i] = buffer_r_p[i] * normalization_scale +
209  scale(buffer_r_h[i]) * (2.0 * buffer_r_n[i] - 1.0) - offset[0];
210  buffer_g_d[i] = buffer_g_p[i] * normalization_scale +
211  scale(buffer_g_h[i]) * (2.0 * buffer_g_n[i] - 1.0) - offset[1];
212  buffer_b_d[i] = buffer_b_p[i] * normalization_scale +
213  scale(buffer_b_h[i]) * (2.0 * buffer_b_n[i] - 1.0) - offset[2];
214  }
215  } else {
216  // Missing heightmap info: we use the position map as our displaced coordinates.
217  wmtk::logger().info("No heightmap provided: using positions as displaced coordinates.");
218  // displaced = positions * normalization_scale - offset
219  for (int i = 0; i < buffer_size; i++) {
220  buffer_r_d[i] = buffer_r_p[i] * normalization_scale - offset[0];
221  buffer_g_d[i] = buffer_g_p[i] * normalization_scale - offset[1];
222  buffer_b_d[i] = buffer_b_p[i] * normalization_scale - offset[2];
223  }
224  }
225 
226  return {{
227  buffer_to_image(buffer_r_d, w_p, h_p),
228  buffer_to_image(buffer_g_d, w_p, h_p),
229  buffer_to_image(buffer_b_d, w_p, h_p),
230  }};
231 }
232 
233 void split_and_save_3channels(const std::filesystem::path& path)
234 {
235  int w, h, channels, index_red, index_blue, index_green;
236  channels = 1;
237  std::vector<float> buffer_r, buffer_g, buffer_b;
238  if (path.extension() == ".exr") {
239  std::tie(w, h, index_red, index_green, index_blue, buffer_r, buffer_g, buffer_b) =
241  assert(!buffer_r.empty());
242  assert(!buffer_g.empty());
243  assert(!buffer_b.empty());
244  } else {
245  logger().error("[split_image] format doesn't support \"{}\"", path.string());
246  return;
247  }
248  const std::filesystem::path directory = path.parent_path();
249  const std::string file = path.stem().string();
250  const std::filesystem::path path_r = directory / (file + "_r.exr");
251  const std::filesystem::path path_g = directory / (file + "_g.exr");
252  const std::filesystem::path path_b = directory / (file + "_b.exr");
253  // just saves single channel data to red channel
254  auto res = save_image_exr_red_channel(w, h, buffer_r, path_r);
255  assert(res);
256  res = save_image_exr_red_channel(w, h, buffer_g, path_g);
257  assert(res);
258  res = save_image_exr_red_channel(w, h, buffer_b, path_b);
259  assert(res);
260 }
261 
262 Image buffer_to_image(const std::vector<float>& buffer, int w, int h)
263 {
264  Image image(w, h);
265  for (int i = 0, k = 0; i < h; i++) {
266  for (int j = 0; j < w; j++) {
267  image.set(h - i - 1, j, buffer[k++]);
268  }
269  }
270  return image;
271 }
272 
273 std::array<Image, 3> load_rgb_image(const std::filesystem::path& path)
274 {
275  int w, h, channels, index_red, index_blue, index_green;
276  channels = 1;
277  std::vector<float> buffer_r, buffer_g, buffer_b;
278  if (path.extension() == ".exr") {
279  std::tie(w, h, index_red, index_green, index_blue, buffer_r, buffer_g, buffer_b) =
281  assert(!buffer_r.empty());
282  assert(!buffer_g.empty());
283  assert(!buffer_b.empty());
284  } else {
285  wmtk::logger().error("[load_rgb_image] format doesn't support \"{}\"", path.string());
286  exit(-1);
287  }
288  return {{
289  buffer_to_image(buffer_r, w, h),
290  buffer_to_image(buffer_g, w, h),
291  buffer_to_image(buffer_b, w, h),
292  }};
293 }
294 } // namespace wmtk::components::adaptive_tessellation::image
int get_coordinate(const int x, const WrappingMode mode) const
Definition: Image.cpp:26
void load(const std::filesystem::path &path, WrappingMode mode_x, WrappingMode mode_y)
Definition: Image.cpp:113
void set_wrapping_mode(WrappingMode mode_x, WrappingMode mode_y)
Definition: Image.hpp:60
std::pair< int, int > get_pixel_index(const double &u, const double &v) const
Definition: Image.cpp:46
bool save(const std::filesystem::path &path) const
Definition: Image.cpp:84
bool set(const std::function< float(const double &, const double &)> &f, const WrappingMode mode_x=WrappingMode::CLAMP_TO_EDGE, const WrappingMode mode_y=WrappingMode::CLAMP_TO_EDGE)
Definition: Image.cpp:61
auto load_image_exr_red_channel(const std::filesystem::path &path) -> std::tuple< size_t, size_t, std::vector< float >>
auto load_image_exr_split_3channels(const std::filesystem::path &path) -> std::tuple< size_t, size_t, int, int, int, std::vector< float >, std::vector< float >, std::vector< float >>
std::array< Image, 3 > load_rgb_image(const std::filesystem::path &path)
Definition: Image.cpp:273
std::array< Image, 3 > combine_position_normal_texture(double normalization_scale, const Eigen::Matrix< double, 1, 3 > &offset, const std::filesystem::path &position_path, const std::filesystem::path &normal_path, const std::filesystem::path &height_path, float min_height, float max_height)
Definition: Image.cpp:163
Image buffer_to_image(const std::vector< float > &buffer, int w, int h)
Definition: Image.cpp:262
bool save_image_exr_red_channel(size_t width, size_t height, const std::vector< float > &data, const std::filesystem::path &path)
void split_and_save_3channels(const std::filesystem::path &path)
Definition: Image.cpp:233
spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:58