VOXELFORMAT: added support for extracting the screenshot from voxel formats

this can e.g. be used by the thumbnailer for those formats that are not yet fully supported.
Also the embedded thumbnail has priority over rendering one ourselves
master
Martin Gerhardy 2021-11-10 18:08:28 +01:00
parent cf625cc002
commit e49f54f8f3
8 changed files with 169 additions and 50 deletions

View File

@ -5,12 +5,17 @@
#pragma once
#include "core/String.h"
#include <stdint.h>
namespace io {
#define VOX_FORMAT_FLAG_SCREENSHOT_EMBEDDED (1 << 0)
struct FormatDescription {
const char *name;
const char *ext; /**< just the file extension */
bool (*isA)(uint32_t magic);
uint32_t flags;
inline core::String wildCard() const {
static const core::String w("*.");
@ -23,12 +28,12 @@ extern core::String getWildcardsFromPattern(const core::String &pattern);
namespace format {
inline const FormatDescription* png() {
static FormatDescription desc{"Image", "png"};
static FormatDescription desc{"Image", "png", nullptr, 0u};
return &desc;
}
inline const FormatDescription* lua() {
static FormatDescription desc{"LUA script", "lua"};
static FormatDescription desc{"LUA script", "lua", nullptr, 0u};
return &desc;
}

View File

@ -10,6 +10,7 @@
#include "core/Color.h"
#include "core/Assert.h"
#include "core/Log.h"
#include "image/Image.h"
namespace voxel {
@ -19,6 +20,12 @@ namespace voxel {
return false; \
}
#define wrapImg(read) \
if ((read) != 0) { \
Log::error("Could not load qbcl screenshot file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
return image::ImagePtr(); \
}
#define wrapBool(read) \
if ((read) == false) { \
Log::error("Could not load qbcl file: Not enough data in stream " CORE_STRINGIFY(read) " - still %i bytes left", (int)stream.remaining()); \
@ -93,7 +100,7 @@ bool QBCLFormat::readCompound(io::FileStream &stream) {
bool QBCLFormat::loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) {
if (!(bool)file || !file->exists()) {
Log::error("Could not load qb file: File doesn't exist");
Log::error("Could not load qbcl file: File doesn't exist");
return false;
}
io::FileStream stream(file.get());
@ -162,7 +169,40 @@ bool QBCLFormat::loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) {
return false;
}
image::ImagePtr QBCLFormat::loadScreenshot(const io::FilePtr& file) {
if (!(bool)file || !file->exists()) {
Log::error("Could not load qbcl file: File doesn't exist");
return image::ImagePtr();
}
io::FileStream stream(file.get());
uint32_t magic;
wrapImg(stream.readInt(magic))
if (magic != FourCC('Q', 'B', 'C', 'L')) {
Log::error("Invalid magic found - no qbcl file");
return image::ImagePtr();
}
wrapImg(stream.readInt(_version))
uint32_t flags;
wrapImg(stream.readInt(flags))
uint32_t thumbWidth;
wrapImg(stream.readInt(thumbWidth))
uint32_t thumbHeight;
wrapImg(stream.readInt(thumbHeight))
image::ImagePtr img = image::createEmptyImage(file->name());
const uint32_t thumbnailSize = thumbWidth * thumbHeight * 4;
uint8_t* buf = new uint8_t[thumbnailSize];
stream.readBuf(buf, thumbnailSize);
if (!img->load(buf, (int)thumbnailSize)) {
delete [] buf;
return image::ImagePtr();
}
delete [] buf;
return img;
}
}
#undef wrapImg
#undef wrap
#undef wrapBool

View File

@ -22,6 +22,7 @@ private:
bool readModel(io::FileStream &stream);
bool readCompound(io::FileStream &stream);
public:
image::ImagePtr loadScreenshot(const io::FilePtr& file) override;
bool loadGroups(const io::FilePtr& file, VoxelVolumes& volumes) override;
bool saveGroups(const VoxelVolumes& volumes, const io::FilePtr& file) override;
};

View File

@ -7,6 +7,8 @@
#include "core/Log.h"
#include "core/StringUtil.h"
#include "core/Trace.h"
#include "io/FormatDescription.h"
#include "video/Texture.h"
#include "voxelformat/PLYFormat.h"
#include "voxelformat/VoxFormat.h"
#include "voxelformat/QBTFormat.h"
@ -28,39 +30,46 @@ namespace voxelformat {
// this is the list of supported voxel volume formats that are have importers implemented
const io::FormatDescription SUPPORTED_VOXEL_FORMATS_LOAD[] = {
{"MagicaVoxel", "vox"},
{"Qubicle Binary Tree", "qbt"},
{"Qubicle Binary", "qb"},
//{"Qubicle", "qbcl"},
{"Sandbox VoxEdit", "vxm"},
{"Sandbox VoxEdit", "vxr"},
{"BinVox", "binvox"},
{"CubeWorld", "cub"},
{"Build engine", "kvx"},
{"Ace of Spades", "kv6"},
{"Tiberian Sun", "vxl"},
{"Qubicle Exchange", "qef"},
{"Chronovox", "csm"},
{"Nicks Voxel Model", "nvm"},
{nullptr, nullptr}
{"MagicaVoxel", "vox", [] (uint32_t magic) {return magic == FourCC('V','O','X',' ');}, 0u},
{"Qubicle Binary Tree", "qbt", [] (uint32_t magic) {return magic == FourCC('Q','B',' ','2');}, 0u},
{"Qubicle Binary", "qb", nullptr, 0u},
{"Qubicle", "qbcl", [] (uint32_t magic) {return magic == FourCC('Q','B','C','L');}, VOX_FORMAT_FLAG_SCREENSHOT_EMBEDDED},
{"Sandbox VoxEdit", "vxm", [] (uint32_t magic) {return magic == FourCC('V','X','M','A')
|| magic == FourCC('V','X','M','9') || magic == FourCC('V','X','M','8')
|| magic == FourCC('V','X','M','7') || magic == FourCC('V','X','M','6')
|| magic == FourCC('V','X','M','5') || magic == FourCC('V','X','M','4');}, 0u},
{"Sandbox VoxEdit", "vxr", [] (uint32_t magic) {return magic == FourCC('V','X','R','7') || magic == FourCC('V','X','R','6')
|| magic == FourCC('V','X','R','5') || magic == FourCC('V','X','R','4')
|| magic == FourCC('V','X','R','3') || magic == FourCC('V','X','R','2')
|| magic == FourCC('V','X','R','1');}, 0u},
{"BinVox", "binvox", [] (uint32_t magic) {return magic == FourCC('#','b','i','n');}, 0u},
{"CubeWorld", "cub", nullptr, 0u},
{"Build engine", "kvx", nullptr, 0u},
{"Ace of Spades", "kv6", [] (uint32_t magic) {return magic == FourCC('K','v','x','l');}, 0u},
{"Tiberian Sun", "vxl", [] (uint32_t magic) {return magic == FourCC('V','o','x','e');}, 0u},
{"AceOfSpades", "vxl", nullptr, 0u},
{"Qubicle Exchange", "qef", [] (uint32_t magic) {return magic == FourCC('Q','u','b','i');}, 0u},
{"Chronovox", "csm", [] (uint32_t magic) {return magic == FourCC('.','C','S','M');}, 0u},
{"Nicks Voxel Model", "nvm", [] (uint32_t magic) {return magic == FourCC('.','N','V','M');}, 0u},
{nullptr, nullptr, nullptr, 0u}
};
// this is the list of internal formats that are supported engine-wide (the format we save our own models in)
const char *SUPPORTED_VOXEL_FORMATS_LOAD_LIST[] = { "qb", "vox", nullptr };
// this is the list of supported voxel or mesh formats that have exporters implemented
const io::FormatDescription SUPPORTED_VOXEL_FORMATS_SAVE[] = {
{"MagicaVoxel", "vox"},
{"Qubicle Binary Tree", "qbt"},
{"Qubicle Binary", "qb"},
//{"Qubicle", "qbcl"},
{"Sandbox VoxEdit", "vxm"},
{"BinVox", "binvox"},
{"CubeWorld", "cub"},
{"Build engine", "kvx"},
{"Tiberian Sun", "vxl"},
{"Qubicle Exchange", "qef"},
{"WaveFront OBJ", "obj"},
{"Polygon File Format", "ply"},
{nullptr, nullptr}
{"MagicaVoxel", "vox", nullptr, 0u},
{"Qubicle Binary Tree", "qbt", nullptr, 0u},
{"Qubicle Binary", "qb", nullptr, 0u},
//{"Qubicle", "qbcl", nullptr, 0u},
{"Sandbox VoxEdit", "vxm", nullptr, 0u},
{"BinVox", "binvox", nullptr, 0u},
{"CubeWorld", "cub", nullptr, 0u},
{"Build engine", "kvx", nullptr, 0u},
{"Tiberian Sun", "vxl", nullptr, 0u},
{"Qubicle Exchange", "qef", nullptr, 0u},
{"WaveFront OBJ", "obj", nullptr, 0u},
{"Polygon File Format", "ply", nullptr, 0u},
{nullptr, nullptr, nullptr, 0u}
};
static uint32_t loadMagic(const io::FilePtr& file) {
@ -70,6 +79,56 @@ static uint32_t loadMagic(const io::FilePtr& file) {
return magicWord;
}
static const io::FormatDescription *getDescription(const core::String &ext, uint32_t magic) {
for (const io::FormatDescription *desc = SUPPORTED_VOXEL_FORMATS_LOAD; desc->ext != nullptr; ++desc) {
if (ext != desc->ext) {
continue;
}
if (desc->isA && !desc->isA(magic)) {
continue;
}
return desc;
}
// search again - but this time only the magic bytes...
for (const io::FormatDescription *desc = SUPPORTED_VOXEL_FORMATS_LOAD; desc->ext != nullptr; ++desc) {
if (!desc->isA) {
continue;
}
if (!desc->isA(magic)) {
continue;
}
return desc;
}
Log::warn("Could not find a supported format description for %s", ext.c_str());
return nullptr;
}
image::ImagePtr loadVolumeScreenshot(const io::FilePtr& filePtr) {
if (!filePtr->exists()) {
Log::error("Failed to load screenshot from model file %s. Doesn't exist.", filePtr->name().c_str());
return image::ImagePtr();
}
const uint32_t magic = loadMagic(filePtr);
core_trace_scoped(LoadVolumeScreenshot);
const core::String& fileext = filePtr->extension();
const io::FormatDescription *desc = getDescription(fileext, magic);
if (!(desc->flags & VOX_FORMAT_FLAG_SCREENSHOT_EMBEDDED)) {
Log::warn("Format %s doesn't have a screenshot embedded", desc->name);
return image::ImagePtr();
}
const core::String &ext = desc->ext;
/*if (ext == "vxm") {
voxel::VXMFormat f;
return f.loadScreenshot(filePtr);
} else*/ if (ext == "qbcl") {
voxel::QBCLFormat f;
return f.loadScreenshot(filePtr);
}
Log::error("Failed to load model screenshot from file %s - unsupported file format for extension '%s'",
filePtr->name().c_str(), ext.c_str());
return image::ImagePtr();
}
bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolumes) {
if (!filePtr->exists()) {
Log::error("Failed to load model file %s. Doesn't exist.", filePtr->name().c_str());
@ -79,7 +138,13 @@ bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolume
const uint32_t magic = loadMagic(filePtr);
core_trace_scoped(LoadVolumeFormat);
const core::String& ext = filePtr->extension();
const core::String& fileext = filePtr->extension();
const io::FormatDescription *desc = getDescription(fileext, magic);
if (!(desc->flags & VOX_FORMAT_FLAG_SCREENSHOT_EMBEDDED)) {
Log::warn("Format %s doesn't have screenshot embedded", desc->name);
return false;
}
const core::String ext = desc->ext;
if (ext == "qb") {
voxel::QBFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
@ -100,7 +165,7 @@ bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolume
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "kv6" || magic == FourCC('K','v','x','l')) {
} else if (ext == "kv6") {
voxel::KV6Format f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
@ -110,24 +175,17 @@ bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolume
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "vxm" || magic == FourCC('V','X','M','A')
|| magic == FourCC('V','X','M','9') || magic == FourCC('V','X','M','8')
|| magic == FourCC('V','X','M','7') || magic == FourCC('V','X','M','6')
|| magic == FourCC('V','X','M','5') || magic == FourCC('V','X','M','4')) {
} else if (ext == "vxm") {
voxel::VXMFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "vxr"
|| magic == FourCC('V','X','R','7') || magic == FourCC('V','X','R','6')
|| magic == FourCC('V','X','R','5') || magic == FourCC('V','X','R','4')
|| magic == FourCC('V','X','R','3') || magic == FourCC('V','X','R','2')
|| magic == FourCC('V','X','R','1')) {
} else if (ext == "vxr") {
voxel::VXRFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "vxl" && magic == FourCC('V','o','x','e')) {
} else if (ext == "vxl" && !strcmp(desc->name, "Tiberian Sun")) {
voxel::VXLFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
@ -137,23 +195,22 @@ bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolume
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "csm" || magic == FourCC('.','C','S','M')
|| ext == "nvm" || magic == FourCC('.','N','V','M')) {
} else if (ext == "csm" || ext == "nvm") {
voxel::CSMFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "binvox" || magic == FourCC('#','b','i','n')) {
} else if (ext == "binvox") {
voxel::BinVoxFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "qef" || magic == FourCC('Q','u','b','i')) {
} else if (ext == "qef") {
voxel::QEFFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);
}
} else if (ext == "qbcl" || magic == FourCC('Q','B','C','L')) {
} else if (ext == "qbcl") {
voxel::QBCLFormat f;
if (!f.loadGroups(filePtr, newVolumes)) {
voxelformat::clearVolumes(newVolumes);

View File

@ -7,6 +7,7 @@
#include "io/File.h"
#include "io/FormatDescription.h"
#include "VoxelVolumes.h"
#include "video/Texture.h"
namespace voxelformat {
@ -14,6 +15,7 @@ extern const io::FormatDescription SUPPORTED_VOXEL_FORMATS_LOAD[];
extern const char *SUPPORTED_VOXEL_FORMATS_LOAD_LIST[];
extern const io::FormatDescription SUPPORTED_VOXEL_FORMATS_SAVE[];
extern image::ImagePtr loadVolumeScreenshot(const io::FilePtr& filePtr);
extern bool loadVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& newVolumes);
extern bool saveVolumeFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& volumes);
extern bool saveMeshFormat(const io::FilePtr& filePtr, voxel::VoxelVolumes& volumes);

View File

@ -8,6 +8,7 @@
#include "core/collection/DynamicArray.h"
#include "voxel/RawVolume.h"
#include "io/File.h"
#include "image/Image.h"
#include "VoxelVolumes.h"
#include <glm/fwd.hpp>
@ -34,6 +35,8 @@ protected:
public:
virtual ~VoxFileFormat() = default;
virtual image::ImagePtr loadScreenshot(const io::FilePtr& /*file*/) { return image::ImagePtr(); }
/**
* @brief If the format supports multiple layers or groups, this method will give them to you as single volumes
*/

View File

@ -154,14 +154,24 @@ bool Thumbnailer::renderVolume() {
return success;
}
bool Thumbnailer::saveEmbeddedScreenshot() {
if (!voxelformat::loadVolumeScreenshot(_infile)) {
Log::error("Failed to load screenshot from input file");
return false;
}
return true;
}
app::AppState Thumbnailer::onRunning() {
app::AppState state = Super::onRunning();
if (state != app::AppState::Running) {
return state;
}
if (!renderVolume()) {
_exitCode = 1;
if (!saveEmbeddedScreenshot()) {
if (!renderVolume()) {
_exitCode = 1;
}
}
requestQuit();

View File

@ -28,6 +28,7 @@ private:
voxelrender::RawVolumeRenderer _renderer;
bool renderVolume();
bool saveEmbeddedScreenshot();
public:
Thumbnailer(const metric::MetricPtr& metric, const io::FilesystemPtr& filesystem, const core::EventBusPtr& eventBus, const core::TimeProviderPtr& timeProvider);