🛠️🐜 Antkeeper superbuild with dependencies included https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
6.6 KiB

  1. #define TINYEXR_IMPLEMENTATION
  2. #include "tinyexr.h"
  3. #ifdef __clang__
  4. #pragma clang diagnostic push
  5. #pragma clang diagnostic ignored "-Weverything"
  6. #endif
  7. #define STB_IMAGE_RESIZE_IMPLEMENTATION
  8. #include "stb_image_resize.h"
  9. #define STB_IMAGE_WRITE_IMPLEMENTATION
  10. #include "stb_image_write.h"
  11. #include "cxxopts.hpp"
  12. #ifdef __clang__
  13. #pragma clang diagnostic pop
  14. #endif
  15. namespace {
  16. static void vnormalize(float v[3]) {
  17. const float d2 = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
  18. if (d2 > 1.0e-6f) {
  19. const float inv_d = 1.0f / std::sqrt(d2);
  20. v[0] *= inv_d;
  21. v[1] *= inv_d;
  22. v[2] *= inv_d;
  23. }
  24. return;
  25. }
  26. template<typename T>
  27. static inline T clamp(const T v, const T min_v, const T max_v)
  28. {
  29. return std::max(min_v, std::min(max_v, v));
  30. }
  31. //
  32. // Compute gradient from scalar field.
  33. // dx = (x + 1, y ) - (x, y)
  34. // dy = (x , y + 1) - (x, y)
  35. //
  36. // TODO(syoyo): Use central difference with texel filtering.
  37. //
  38. static void Gradient(
  39. const std::vector<float> &src,
  40. const size_t width,
  41. const size_t height,
  42. const size_t x, const size_t y,
  43. const float bumpness,
  44. float dir[3])
  45. {
  46. const size_t x1 = clamp(x + 1, size_t(0), width - 1);
  47. const size_t y1 = clamp(y + 1, size_t(0), height - 1);
  48. float v00 = src[y * width + x];
  49. float v01 = src[y * width + x1];
  50. float v11 = src[y1 * width + x];
  51. float dx = bumpness * (v01 - v00);
  52. float dy = bumpness * (v11 - v00);
  53. dir[0] = dx;
  54. dir[1] = dy;
  55. dir[2] = 0.0f;
  56. }
  57. ///
  58. /// Convert image(bump map for single channel, vector displacement map for 3 channels input) to normal map.
  59. /// @param[in] base Base value fo
  60. ///
  61. ///
  62. static void ToNormalMap(
  63. const std::vector<float> &src,
  64. const size_t width,
  65. const size_t height,
  66. const size_t channels,
  67. const float strength,
  68. std::vector<float> *dst)
  69. {
  70. assert((channels == 1) || (channels == 3) || (channels == 4));
  71. dst->resize(width * height * 3);
  72. if (channels == 1) {
  73. // bump map
  74. for (size_t y = 0; y < height; y++) {
  75. for (size_t x = 0; x < width; x++) {
  76. float d[3];
  77. Gradient(src, width, height, x, y, strength, d);
  78. (*dst)[3 * (y * width + x) + 0] = d[0];
  79. (*dst)[3 * (y * width + x) + 1] = d[1];
  80. (*dst)[3 * (y * width + x) + 2] = d[2];
  81. }
  82. }
  83. } else {
  84. // vector displacement map
  85. for (size_t y = 0; y < height; y++) {
  86. for (size_t x = 0; x < width; x++) {
  87. float v[3];
  88. v[0] = src[channels * (y * width + x) + 0];
  89. v[1] = src[channels * (y * width + x) + 1];
  90. v[2] = src[channels * (y * width + x) + 2];
  91. v[0] *= strength;
  92. v[1] *= strength;
  93. v[2] *= strength;
  94. // Add (0, 0, 1)
  95. v[2] += 1.0f;
  96. // TODO(syoyo): Add option to not normalize.
  97. vnormalize(v);
  98. (*dst)[3 * (y * width + x) + 0] = 0.5f * v[0] + 0.5f;
  99. (*dst)[3 * (y * width + x) + 1] = 0.5f * v[1] + 0.5f;
  100. (*dst)[3 * (y * width + x) + 2] = 0.5f * v[2] + 0.5f;
  101. }
  102. }
  103. }
  104. }
  105. inline unsigned char ftouc(float f)
  106. {
  107. int i = static_cast<int>(f * 255.0f);
  108. if (i > 255) i = 255;
  109. if (i < 0) i = 0;
  110. return static_cast<unsigned char>(i);
  111. }
  112. bool SaveImage(const char* filename, const float* rgb, int width, int height) {
  113. std::vector<unsigned char> dst(width * height * 3);
  114. for (size_t i = 0; i < width * height; i++) {
  115. dst[i * 3 + 0] = ftouc(rgb[i * 3 + 0]);
  116. dst[i * 3 + 1] = ftouc(rgb[i * 3 + 1]);
  117. dst[i * 3 + 2] = ftouc(rgb[i * 3 + 2]);
  118. }
  119. int ret = stbi_write_png(filename, width, height, 3, static_cast<const void*>(dst.data()), width * 3);
  120. return (ret > 0);
  121. }
  122. std::string GetFileExtension(const std::string &filename) {
  123. if (filename.find_last_of(".") != std::string::npos)
  124. return filename.substr(filename.find_last_of(".") + 1);
  125. return "";
  126. }
  127. } // namespace
  128. int main(int argc, char **argv)
  129. {
  130. cxxopts::Options options("normalmap", "help");
  131. options.add_options()
  132. ("s,strength", "Strength(scaling) for normal value", cxxopts::value<float>())
  133. ("i,input", "Input filename", cxxopts::value<std::string>())
  134. ("o,output", "Output filename", cxxopts::value<std::string>())
  135. ("r,resize", "Resize image. 0.5 = 50%%, 0.1 = 10%%", cxxopts::value<float>())
  136. ;
  137. auto result = options.parse(argc, argv);
  138. if (result.count("input") == 0) {
  139. std::cerr << "input filename missing" << std::endl;
  140. return EXIT_FAILURE;
  141. }
  142. if (result.count("output") == 0) {
  143. std::cerr << "output filename missing" << std::endl;
  144. return EXIT_FAILURE;
  145. }
  146. float strength = 1.0f;
  147. if (result.count("strength")) {
  148. strength = result["strength"].as<float>();
  149. }
  150. float resize = 1.0f;
  151. if (result.count("resize")) {
  152. resize = result["resize"].as<float>();
  153. }
  154. std::string input_filename = result["input"].as<std::string>();
  155. std::string output_filename = result["output"].as<std::string>();
  156. std::vector<float> src;
  157. size_t src_width;
  158. size_t src_height;
  159. {
  160. float *rgba = nullptr;
  161. int width, height;
  162. const char *err = nullptr;
  163. int ret = LoadEXR(&rgba, &width, &height, input_filename.c_str(), &err);
  164. if (TINYEXR_SUCCESS != ret) {
  165. std::cerr << "Failed to load EXR file [" << input_filename << "] code = " << ret << std::endl;
  166. if (err) {
  167. std::cerr << err << std::endl;
  168. FreeEXRErrorMessage(err);
  169. }
  170. return EXIT_FAILURE;
  171. }
  172. std::cout << "loaded EXR. width x height = " << width << "x" << height << std::endl;
  173. src.resize(size_t(width * height * 3));
  174. // ignore alpha for now
  175. for (size_t i = 0; i < size_t(width * height); i++) {
  176. src[3 * i + 0] = rgba[4 * i + 0];
  177. src[3 * i + 1] = rgba[4 * i + 1];
  178. src[3 * i + 2] = rgba[4 * i + 2];
  179. }
  180. src_width = size_t(width);
  181. src_height = size_t(height);
  182. free(rgba);
  183. }
  184. std::cout << "strength = " << strength << std::endl;
  185. std::vector<float> dst;
  186. ToNormalMap(src, src_width, src_height, 3, strength, &dst);
  187. std::string ext = GetFileExtension(output_filename);
  188. if ((ext.compare("png") == 0) ||
  189. (ext.compare("PNG") == 0)) {
  190. // Save as LDR image.
  191. // Do not apply sRGB conversion for PNG(LDR) image.
  192. if (!SaveImage(output_filename.c_str(), dst.data(), int(src_width), int(src_height))) {
  193. std::cerr << "Failed to write a file : " << output_filename << std::endl;
  194. return EXIT_FAILURE;
  195. }
  196. } else {
  197. // assume EXR.
  198. float *rgba = nullptr;
  199. int width, height;
  200. int ret = SaveEXR(dst.data(), int(src_width), int(src_height), /* component */3, /* fp16 */0, output_filename.c_str(), nullptr);
  201. if (TINYEXR_SUCCESS != ret) {
  202. std::cerr << "Failed to save EXR file [" << input_filename << "] code = " << ret << std::endl;
  203. return EXIT_FAILURE;
  204. }
  205. }
  206. return EXIT_SUCCESS;
  207. }