mirror of
https://git.mirrors.martin98.com/https://github.com/syoyo/tinygltf.git
synced 2025-08-12 16:39:02 +08:00
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.
This commit is contained in:
parent
7bdfed3bec
commit
758a1240c9
@ -49,6 +49,7 @@ struct configuration {
|
|||||||
cli_action action = cli_action::not_set;
|
cli_action action = cli_action::not_set;
|
||||||
texture_dumper::texture_output_format requested_format =
|
texture_dumper::texture_output_format requested_format =
|
||||||
texture_dumper::texture_output_format::not_specified;
|
texture_dumper::texture_output_format::not_specified;
|
||||||
|
bool use_exr = false;
|
||||||
|
|
||||||
bool has_output_dir;
|
bool has_output_dir;
|
||||||
bool is_valid() {
|
bool is_valid() {
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
#include "stb_image_write.h"
|
#include "stb_image_write.h"
|
||||||
|
|
||||||
|
#define TINYEXR_IMPLEMENTATION
|
||||||
|
#include "tinyexr.h"
|
||||||
|
|
||||||
namespace gltfutil {
|
namespace gltfutil {
|
||||||
int usage(int ret = 0) {
|
int usage(int ret = 0) {
|
||||||
using std::cout;
|
using std::cout;
|
||||||
@ -22,6 +25,7 @@ int usage(int ret = 0) {
|
|||||||
<< "\t\t -d: dump enclosed content (image assets)\n"
|
<< "\t\t -d: dump enclosed content (image assets)\n"
|
||||||
<< "\t\t -f: file format for image output\n"
|
<< "\t\t -f: file format for image output\n"
|
||||||
<< "\t\t -o: ouptput directory path\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";
|
<< "\t\t -h: print this help\n";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -44,6 +48,9 @@ int parse_args(int argc, char** argv) {
|
|||||||
config.mode = ui_mode::cli;
|
config.mode = ui_mode::cli;
|
||||||
config.action = cli_action::dump;
|
config.action = cli_action::dump;
|
||||||
break;
|
break;
|
||||||
|
case 'e':
|
||||||
|
config.use_exr = true;
|
||||||
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
config.mode = ui_mode::interactive;
|
config.mode = ui_mode::interactive;
|
||||||
break;
|
break;
|
||||||
@ -97,6 +104,11 @@ int parse_args(int argc, char** argv) {
|
|||||||
|
|
||||||
case cli_action::dump: {
|
case cli_action::dump: {
|
||||||
texture_dumper dumper(model);
|
texture_dumper dumper(model);
|
||||||
|
|
||||||
|
if (config.use_exr) {
|
||||||
|
dumper.set_use_exr(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.requested_format !=
|
if (config.requested_format !=
|
||||||
texture_dumper::texture_output_format::not_specified)
|
texture_dumper::texture_output_format::not_specified)
|
||||||
dumper.set_output_format(config.requested_format);
|
dumper.set_output_format(config.requested_format);
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
#include "stb_image_write.h"
|
#include "stb_image_write.h"
|
||||||
#include "texture_dumper.h"
|
#include "texture_dumper.h"
|
||||||
|
|
||||||
#include "lodepng.h" // ../common
|
#include "lodepng.h" // ../common
|
||||||
|
|
||||||
|
#include "tinyexr.h"
|
||||||
|
|
||||||
#include <tiny_gltf.h>
|
#include <tiny_gltf.h>
|
||||||
|
|
||||||
@ -12,22 +14,81 @@ using namespace gltfutil;
|
|||||||
using namespace tinygltf;
|
using namespace tinygltf;
|
||||||
using std::cout;
|
using std::cout;
|
||||||
|
|
||||||
static LodePNGColorType GetLodePNGColorType(int channels)
|
static LodePNGColorType GetLodePNGColorType(int channels) {
|
||||||
{
|
|
||||||
if (channels == 1) {
|
if (channels == 1) {
|
||||||
return LodePNGColorType::LCT_GREY;
|
return LodePNGColorType::LCT_GREY;
|
||||||
} else if (channels == 2) {
|
} else if (channels == 2) {
|
||||||
return LodePNGColorType::LCT_GREY_ALPHA;
|
return LodePNGColorType::LCT_GREY_ALPHA;
|
||||||
} else if (channels == 3) {
|
} else if (channels == 3) {
|
||||||
return LodePNGColorType::LCT_RGB;
|
return LodePNGColorType::LCT_RGB;
|
||||||
} else if (channels == 4) {
|
} else if (channels == 4) {
|
||||||
return LodePNGColorType::LCT_RGBA;
|
return LodePNGColorType::LCT_RGBA;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "??? unsupported channels " << channels << "\n";
|
std::cerr << "??? unsupported channels " << channels << "\n";
|
||||||
return LodePNGColorType::LCT_RGB; // FIXME(syoyo): Raise error
|
return LodePNGColorType::LCT_RGB; // FIXME(syoyo): Raise error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ToBigEndian(std::vector<uint8_t>* 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<uint8_t*>(val);
|
||||||
|
uint8_t* src = reinterpret_cast<uint8_t*>(&tmp);
|
||||||
|
|
||||||
|
dst[0] = src[1];
|
||||||
|
dst[1] = src[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t *ptr = reinterpret_cast<uint16_t *>(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<float> 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<const unsigned short*>(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)
|
texture_dumper::texture_dumper(const Model& input)
|
||||||
: model(input), configured_format(texture_output_format::png) {
|
: model(input), configured_format(texture_output_format::png) {
|
||||||
cout << "Texture dumper\n";
|
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 << "image size is: " << image.width << 'x' << image.height << '\n';
|
||||||
cout << "pixel channel count :" << image.component << '\n';
|
cout << "pixel channel count :" << image.component << '\n';
|
||||||
cout << "pixel bit depth :" << image.bits << '\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 =
|
unsigned char* bytes_to_write =
|
||||||
const_cast<unsigned char*>(image.image.data());
|
const_cast<unsigned char*>(image.image.data());
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
switch (configured_format) {
|
switch (configured_format) {
|
||||||
case texture_output_format::png:
|
case texture_output_format::png:
|
||||||
name = path + "/" + name + ".png";
|
filename = path + "/" + basename + ".png";
|
||||||
std::cout << "Image will be written to " << name << '\n';
|
|
||||||
|
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) {
|
if (image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
|
||||||
// Use lodepng to save 16bit PNG.
|
if (this->use_exr) {
|
||||||
// TODO(syoyo): check status
|
bool ret = Save16bitImageAsEXR(filename, image);
|
||||||
unsigned ret = lodepng::encode(name, bytes_to_write, image.width, image.height, GetLodePNGColorType(image.component), /* bits */16);
|
assert(ret);
|
||||||
assert(ret == 0); // 0 = no err.
|
} else {
|
||||||
|
// Use lodepng to save 16bit PNG.
|
||||||
|
// NOTE(syoyo): `loadpng::encode` requires image data must be stored in big endian.
|
||||||
|
std::vector<uint8_t> 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 {
|
} else {
|
||||||
// TODO(syoyo): check status
|
// TODO(syoyo): check status
|
||||||
stbi_write_png(name.c_str(), image.width, image.height, image.component,
|
stbi_write_png(filename.c_str(), image.width, image.height,
|
||||||
bytes_to_write, 0);
|
image.component, bytes_to_write, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case texture_output_format::bmp:
|
case texture_output_format::bmp:
|
||||||
std::cout << "Image will be written to " << name << '\n';
|
filename = path + "/" + basename + ".bmp";
|
||||||
name = path + "/" + name + ".bmp";
|
std::cout << "Image will be written to " << filename << '\n';
|
||||||
stbi_write_bmp(name.c_str(), image.width, image.height, image.component,
|
stbi_write_bmp(filename.c_str(), image.width, image.height,
|
||||||
bytes_to_write);
|
image.component, bytes_to_write);
|
||||||
break;
|
break;
|
||||||
case texture_output_format::tga:
|
case texture_output_format::tga:
|
||||||
std::cout << "Image will be written to " << name << '\n';
|
filename = path + "/" + basename + ".tga";
|
||||||
name = path + "/" + name + ".tga";
|
std::cout << "Image will be written to " << filename << '\n';
|
||||||
stbi_write_tga(name.c_str(), image.width, image.height, image.component,
|
stbi_write_tga(filename.c_str(), image.width, image.height,
|
||||||
bytes_to_write);
|
image.component, bytes_to_write);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,15 @@ class texture_dumper {
|
|||||||
private:
|
private:
|
||||||
const tinygltf::Model& model;
|
const tinygltf::Model& model;
|
||||||
texture_output_format configured_format;
|
texture_output_format configured_format;
|
||||||
|
bool use_exr = false; // Use EXR for 16bit image?
|
||||||
|
|
||||||
public:
|
public:
|
||||||
texture_dumper(const tinygltf::Model& inputModel);
|
texture_dumper(const tinygltf::Model& inputModel);
|
||||||
void dump_to_folder(const std::string& path = "./");
|
void dump_to_folder(const std::string& path = "./");
|
||||||
void set_output_format(texture_output_format format);
|
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);
|
static texture_output_format get_fromat_from_string(const std::string& str);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user