From 758a1240c938e2c644936924abdd9909ab4704c9 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Sun, 3 Mar 2019 21:21:18 +0900 Subject: [PATCH] Reorder 16 bit pixel data to big endian when saving it as 16 png, since lodepng::encode expects image data is in big endian manner. Add OpenEXR saver for 16bit image as a bonus. --- examples/gltfutil/gltfuilconfig.h | 1 + examples/gltfutil/main.cc | 12 +++ examples/gltfutil/texture_dumper.cc | 128 ++++++++++++++++++++++------ examples/gltfutil/texture_dumper.h | 4 + 4 files changed, 121 insertions(+), 24 deletions(-) diff --git a/examples/gltfutil/gltfuilconfig.h b/examples/gltfutil/gltfuilconfig.h index b6bb196..21958c1 100644 --- a/examples/gltfutil/gltfuilconfig.h +++ b/examples/gltfutil/gltfuilconfig.h @@ -49,6 +49,7 @@ struct configuration { cli_action action = cli_action::not_set; texture_dumper::texture_output_format requested_format = texture_dumper::texture_output_format::not_specified; + bool use_exr = false; bool has_output_dir; bool is_valid() { diff --git a/examples/gltfutil/main.cc b/examples/gltfutil/main.cc index 5d080fa..3260044 100644 --- a/examples/gltfutil/main.cc +++ b/examples/gltfutil/main.cc @@ -11,6 +11,9 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" +#define TINYEXR_IMPLEMENTATION +#include "tinyexr.h" + namespace gltfutil { int usage(int ret = 0) { using std::cout; @@ -22,6 +25,7 @@ int usage(int ret = 0) { << "\t\t -d: dump enclosed content (image assets)\n" << "\t\t -f: file format for image output\n" << "\t\t -o: ouptput directory path\n" + << "\t\t -e: Use OpenEXR format for 16bit image\n" << "\t\t -h: print this help\n"; return ret; } @@ -44,6 +48,9 @@ int parse_args(int argc, char** argv) { config.mode = ui_mode::cli; config.action = cli_action::dump; break; + case 'e': + config.use_exr = true; + break; case 'i': config.mode = ui_mode::interactive; break; @@ -97,6 +104,11 @@ int parse_args(int argc, char** argv) { case cli_action::dump: { texture_dumper dumper(model); + + if (config.use_exr) { + dumper.set_use_exr(true); + } + if (config.requested_format != texture_dumper::texture_output_format::not_specified) dumper.set_output_format(config.requested_format); diff --git a/examples/gltfutil/texture_dumper.cc b/examples/gltfutil/texture_dumper.cc index 06fd7fe..c46bc70 100644 --- a/examples/gltfutil/texture_dumper.cc +++ b/examples/gltfutil/texture_dumper.cc @@ -4,7 +4,9 @@ #include "stb_image_write.h" #include "texture_dumper.h" -#include "lodepng.h" // ../common +#include "lodepng.h" // ../common + +#include "tinyexr.h" #include @@ -12,22 +14,81 @@ using namespace gltfutil; using namespace tinygltf; using std::cout; -static LodePNGColorType GetLodePNGColorType(int channels) -{ +static LodePNGColorType GetLodePNGColorType(int channels) { if (channels == 1) { - return LodePNGColorType::LCT_GREY; + return LodePNGColorType::LCT_GREY; } else if (channels == 2) { - return LodePNGColorType::LCT_GREY_ALPHA; + return LodePNGColorType::LCT_GREY_ALPHA; } else if (channels == 3) { - return LodePNGColorType::LCT_RGB; + return LodePNGColorType::LCT_RGB; } else if (channels == 4) { - return LodePNGColorType::LCT_RGBA; + return LodePNGColorType::LCT_RGBA; } else { std::cerr << "??? unsupported channels " << channels << "\n"; return LodePNGColorType::LCT_RGB; // FIXME(syoyo): Raise error } } +static void ToBigEndian(std::vector* image) { + + assert(image->size() % 2 == 0); + + union { + unsigned int i; + char c[4]; + } bint = {0x01020304}; + + bool is_big_endian = (bint.c[0] == 1); + + if (is_big_endian) { + return; + } + + auto swap2 = + [](uint16_t* val) { + uint16_t tmp = *val; + uint8_t* dst = reinterpret_cast(val); + uint8_t* src = reinterpret_cast(&tmp); + + dst[0] = src[1]; + dst[1] = src[0]; + }; + + uint16_t *ptr = reinterpret_cast(image->data()); + size_t n = image->size() / 2; + + for (size_t i = 0; i < n; i++) { + swap2(&ptr[i]); + } +} + +static bool Save16bitImageAsEXR(const std::string& filename, + const tinygltf::Image& image) { + assert(image.bits == 16); + + std::vector buf(image.width * image.height * image.component); + + // widen to float image. + // Store as is(i.e, pixel value range is [0.0, 65535.0]) + const unsigned short* ptr = + reinterpret_cast(image.image.data()); + for (size_t i = 0; i < image.width * image.height * image.component; i++) { + buf[i] = float(ptr[i]); + } + + const char* err = nullptr; + int ret = SaveEXR(buf.data(), image.width, image.height, image.component, + /* save_as_fp16 */ 0, filename.c_str(), &err); + + if (err) { + std::cerr << "EXR err: " << err << std::endl; + FreeEXRErrorMessage(err); + return false; + } + + return (ret == TINYEXR_SUCCESS); +} + texture_dumper::texture_dumper(const Model& input) : model(input), configured_format(texture_output_format::png) { cout << "Texture dumper\n"; @@ -45,38 +106,57 @@ void texture_dumper::dump_to_folder(const std::string& path) { cout << "image size is: " << image.width << 'x' << image.height << '\n'; cout << "pixel channel count :" << image.component << '\n'; cout << "pixel bit depth :" << image.bits << '\n'; - std::string name = image.name.empty() ? std::to_string(index) : image.name; + std::string basename = + image.name.empty() ? std::to_string(index) : image.name; unsigned char* bytes_to_write = const_cast(image.image.data()); + std::string filename; switch (configured_format) { case texture_output_format::png: - name = path + "/" + name + ".png"; - std::cout << "Image will be written to " << name << '\n'; + filename = path + "/" + basename + ".png"; + + if (this->use_exr) { + if (image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { + filename = path + "/" + basename + ".exr"; + } + } + + std::cout << "Image will be written to " << filename << '\n'; if (image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { - // Use lodepng to save 16bit PNG. - // TODO(syoyo): check status - unsigned ret = lodepng::encode(name, bytes_to_write, image.width, image.height, GetLodePNGColorType(image.component), /* bits */16); - assert(ret == 0); // 0 = no err. + if (this->use_exr) { + bool ret = Save16bitImageAsEXR(filename, image); + assert(ret); + } else { + // Use lodepng to save 16bit PNG. + // NOTE(syoyo): `loadpng::encode` requires image data must be stored in big endian. + std::vector tmp = image.image; // copy + ToBigEndian(&tmp); + + unsigned ret = lodepng::encode( + filename, tmp.data(), image.width, image.height, + GetLodePNGColorType(image.component), /* bits */ 16); + assert(ret == 0); // 0 = no err. + } } else { // TODO(syoyo): check status - stbi_write_png(name.c_str(), image.width, image.height, image.component, - bytes_to_write, 0); + stbi_write_png(filename.c_str(), image.width, image.height, + image.component, bytes_to_write, 0); } break; case texture_output_format::bmp: - std::cout << "Image will be written to " << name << '\n'; - name = path + "/" + name + ".bmp"; - stbi_write_bmp(name.c_str(), image.width, image.height, image.component, - bytes_to_write); + filename = path + "/" + basename + ".bmp"; + std::cout << "Image will be written to " << filename << '\n'; + stbi_write_bmp(filename.c_str(), image.width, image.height, + image.component, bytes_to_write); break; case texture_output_format::tga: - std::cout << "Image will be written to " << name << '\n'; - name = path + "/" + name + ".tga"; - stbi_write_tga(name.c_str(), image.width, image.height, image.component, - bytes_to_write); + filename = path + "/" + basename + ".tga"; + std::cout << "Image will be written to " << filename << '\n'; + stbi_write_tga(filename.c_str(), image.width, image.height, + image.component, bytes_to_write); break; } } diff --git a/examples/gltfutil/texture_dumper.h b/examples/gltfutil/texture_dumper.h index bd30318..890297a 100644 --- a/examples/gltfutil/texture_dumper.h +++ b/examples/gltfutil/texture_dumper.h @@ -12,11 +12,15 @@ class texture_dumper { private: const tinygltf::Model& model; texture_output_format configured_format; + bool use_exr = false; // Use EXR for 16bit image? public: texture_dumper(const tinygltf::Model& inputModel); void dump_to_folder(const std::string& path = "./"); void set_output_format(texture_output_format format); + void set_use_exr(const bool value) { + use_exr = value; + } static texture_output_format get_fromat_from_string(const std::string& str); };