VOXEDIT: allow to change and create the palette at runtime

master
Martin Gerhardy 2019-05-15 21:45:13 +02:00
parent 430a646d63
commit 4b353830d5
15 changed files with 178 additions and 36 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,3 +1,4 @@
createpalette images/01.png
importplane images/01.png
importplane images/02.png
importplane images/03.png

View File

@ -70,9 +70,8 @@ int Color::getClosestMatch(const glm::vec4& color, const std::vector<glm::vec4>&
const float val = weightHue * glm::pow(dH, 2) +
weightValue * glm::pow(dV, 2) +
weightSaturation * glm::pow(dS, 2);
const float curDistance = sqrtf(val);
if (curDistance < minDistance) {
minDistance = curDistance;
if (val < minDistance) {
minDistance = val;
minIndex = i;
}
}

View File

@ -69,7 +69,7 @@ ImagePtr loadImage(const std::string& filename, bool async) {
return loadImage(file, async);
}
bool Image::load(uint8_t* buffer, int length) {
bool Image::load(const uint8_t* buffer, int length) {
if (!buffer || length <= 0) {
_state = io::IOSTATE_FAILED;
Log::debug("Failed to load image %s: buffer empty", _name.c_str());

View File

@ -26,7 +26,7 @@ public:
~Image();
bool load(const io::FilePtr& file);
bool load(uint8_t* buffer, int length);
bool load(const uint8_t* buffer, int length);
static void flipVerticalRGBA(uint8_t *pixels, int w, int h);
static bool writePng(const char *name, const uint8_t *buffer, int width, int height, int depth);

View File

@ -3,7 +3,6 @@
*/
#include "MaterialColor.h"
#include "image/Image.h"
#include "core/Singleton.h"
#include "core/App.h"
#include "math/Random.h"
@ -19,40 +18,28 @@ namespace voxel {
class MaterialColor {
private:
image::Image _image;
MaterialColorArray _materialColors;
std::unordered_map<VoxelType, MaterialColorIndices, EnumClassHash> _colorMapping;
bool _initialized = false;
bool _dirty = false;
public:
MaterialColor() :
_image("**palette**") {
MaterialColor() {
}
bool init(const io::FilePtr& paletteFile, const io::FilePtr& luaFile) {
bool init(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaString) {
if (_initialized) {
Log::debug("MaterialColors are already initialized");
return true;
}
_initialized = true;
if (!_image.load(paletteFile)) {
Log::error("MaterialColors: failed to load image");
return false;
}
if (!_image.isLoaded()) {
Log::error("MaterialColors: image not fully loaded");
return false;
}
const int colors = _image.width() * _image.height();
_dirty = true;
const int colors = paletteBufferSize / 4;
if (colors != 256) {
Log::error("Palette image has invalid dimensions: %i:%i", _image.width(), _image.height());
return false;
}
if (_image.depth() != 4) {
Log::error("Palette image has invalid depth: %i", _image.depth());
Log::error("Palette image has invalid dimensions - we need 256x1(depth: 4)");
return false;
}
_materialColors.reserve(colors);
const uint32_t* paletteData = (const uint32_t*)_image.data();
const uint32_t* paletteData = (const uint32_t*)paletteBuffer;
for (int i = 0; i < colors; ++i) {
_materialColors.emplace_back(core::Color::fromRGBA(*paletteData));
++paletteData;
@ -68,6 +55,11 @@ public:
// 0 is VoxelType::Air - don't add it
std::iota(std::begin(generic), std::end(generic), 1);
if (luaString.empty()) {
Log::warn("No materials defined in lua script");
return true;
}
std::vector<luaL_Reg> funcs;
static_assert((int)voxel::VoxelType::Air == 0, "Air must be 0");
for (int i = (int)voxel::VoxelType::Air + 1; i < (int)voxel::VoxelType::Max; ++i) {
@ -110,19 +102,14 @@ public:
funcs.push_back({ nullptr, nullptr });
lua.newGlobalData<MaterialColor>("MaterialColor", this);
lua.reg("MAT", &funcs.front());
const std::string& luaString = luaFile->load();
if (luaString.empty()) {
Log::error("Could not load lua script file: %s", luaFile->fileName().c_str());
return false;
}
if (!lua.load(luaString)) {
Log::error("Could not load lua script: %s. Failed with error: %s",
luaFile->fileName().c_str(), lua.error().c_str());
Log::error("Could not load lua script. Failed with error: %s",
lua.error().c_str());
return false;
}
if (!lua.execute("init")) {
Log::error("Could not execute lua script file: %s. Failed with error: %s",
luaFile->fileName().c_str(), lua.error().c_str());
Log::error("Could not execute lua script. Failed with error: %s",
lua.error().c_str());
return false;
}
@ -137,6 +124,21 @@ public:
return true;
}
void shutdown() {
_materialColors.clear();
_colorMapping.clear();
_initialized = false;
_dirty = false;
}
inline void markClean() {
_dirty = false;
}
inline bool isDirty() const {
return _dirty;
}
inline const MaterialColorArray& getColors() const {
core_assert_msg(_initialized, "Material colors are not yet initialized");
core_assert_msg(!_materialColors.empty(), "Failed to initialize the material colors");
@ -200,16 +202,45 @@ static MaterialColor& getInstance() {
return color;
}
bool initMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer) {
return getInstance().init(paletteBuffer, paletteBufferSize, luaBuffer);
}
bool overrideMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer) {
shutdownMaterialColors();
if (!initMaterialColors(paletteBuffer, paletteBufferSize, luaBuffer)) {
return initDefaultMaterialColors();
}
return true;
}
bool materialColorChanged() {
return getInstance().isDirty();
}
void materialColorMarkClean() {
getInstance().markClean();
}
void shutdownMaterialColors() {
return getInstance().shutdown();
}
bool initMaterialColors(const io::FilePtr& paletteFile, const io::FilePtr& luaFile) {
if (!paletteFile->exists()) {
Log::error("Failed to load %s", paletteFile->name().c_str());
Log::error("%s doesn't exist", paletteFile->name().c_str());
return false;
}
if (!luaFile->exists()) {
Log::error("Failed to load %s", luaFile->name().c_str());
return false;
}
return getInstance().init(paletteFile, luaFile);
const image::ImagePtr& img = image::loadImage(paletteFile, false);
if (!img->isLoaded()) {
Log::error("Failed to load image %s", paletteFile->name().c_str());
return false;
}
return initMaterialColors(img->data(), img->width() * img->height() * img->depth(), luaFile->load());
}
bool initDefaultMaterialColors() {
@ -244,4 +275,41 @@ Voxel createRandomColorVoxel(VoxelType type, math::Random& random) {
return getInstance().createRandomColorVoxel(type, random);
}
bool createPalette(const image::ImagePtr& image, uint32_t *colorsBuffer, int colors) {
if (!image || !image->isLoaded()) {
return false;
}
const int imageWidth = image->width();
const int imageHeight = image->height();
Log::info("Create palette for image: %s", image->name().c_str());
uint16_t paletteIndex = 0;
std::unordered_set<uint32_t> colorset;
for (int x = 0; x < imageWidth; ++x) {
for (int y = 0; y < imageHeight; ++y) {
const uint8_t* data = image->at(x, y);
uint32_t rgba = *(uint32_t*)data;
if (colorset.insert(rgba).second) {
if (paletteIndex >= colors) {
Log::info("palette indices exceeded");
return false;
}
colorsBuffer[paletteIndex++] = rgba;
}
}
}
return true;
}
bool createPaletteFile(const image::ImagePtr& image, const char *paletteFile) {
if (!image || !image->isLoaded() || paletteFile == nullptr) {
return false;
}
uint32_t buf[256];
if (!createPalette(image, buf, lengthof(buf))) {
return false;
}
const image::ImagePtr& paletteImg = image::createEmptyImage("**palette**");
return paletteImg->writePng(paletteFile, (const uint8_t*)buf, 256, 1, 4);
}
}

View File

@ -6,6 +6,7 @@
#include "voxel/polyvox/Voxel.h"
#include "io/File.h"
#include "image/Image.h"
#include <glm/fwd.hpp>
#include <glm/vec4.hpp>
@ -23,8 +24,17 @@ typedef std::vector<uint8_t> MaterialColorIndices;
extern bool initDefaultMaterialColors();
extern bool initMaterialColors(const io::FilePtr& paletteFile, const io::FilePtr& luaFile);
extern bool initMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer);
extern bool overrideMaterialColors(const uint8_t* paletteBuffer, size_t paletteBufferSize, const std::string& luaBuffer);
extern void shutdownMaterialColors();
extern void materialColorMarkClean();
extern bool materialColorChanged();
extern const MaterialColorArray& getMaterialColors();
extern const glm::vec4& getMaterialColor(const Voxel& voxel);
extern bool createPalette(const image::ImagePtr& image, uint32_t *colorsBuffer, int colors);
extern bool createPaletteFile(const image::ImagePtr& image, const char *paletteFile);
/**
* @brief Get all known material color indices for the given VoxelType
* @param type The VoxelType to get the indices for

View File

@ -116,6 +116,14 @@ bool RawVolumeRenderer::update(int idx) {
std::vector<voxel::VoxelVertex> vertices;
std::vector<voxel::IndexType> indices;
if (voxel::materialColorChanged()) {
shader::Materialblock::Data materialBlock;
memcpy(materialBlock.materialcolor, &voxel::getMaterialColors().front(), sizeof(materialBlock.materialcolor));
_materialBlock.update(materialBlock);
// TODO: updating the global state is crap - what about others - use an event
voxel::materialColorMarkClean();
}
voxel::IndexType offset = (voxel::IndexType)0;
for (auto& i : _meshes) {
const Meshes& meshes = i.second;

View File

@ -117,6 +117,7 @@ set(FILES
voxedit/images/05.png
voxedit/images/06.png
voxedit/images/07.png
voxedit/images/avatar.png
voxedit/voxedit-keybindings.cfg

View File

@ -24,13 +24,20 @@ set(TEST_SRCS
tests/LayerManagerTest.cpp
tests/MementoHandlerTest.cpp
tests/ModifierTest.cpp
tests/ImageUtilsTest.cpp
)
set(TEST_FILES
tests/test-palette-in.png
)
gtest_suite_sources(tests
${TEST_SRCS}
)
gtest_suite_deps(tests ${LIB})
gtest_suite_files(tests ${TEST_FILES})
gtest_suite_begin(tests-${LIB} TEMPLATE ${ROOT_DIR}/src/modules/core/tests/main.cpp.in)
gtest_suite_sources(tests-${LIB} ${TEST_SRCS} ../../../modules/core/tests/AbstractTest.cpp)
gtest_suite_files(tests-${LIB} ${TEST_FILES})
gtest_suite_deps(tests-${LIB} ${LIB})
gtest_suite_end(tests-${LIB})

View File

@ -139,6 +139,22 @@ bool SceneManager::voxelizeModel(const video::MeshPtr& meshPtr) {
return true;
}
bool SceneManager::importPalette(const std::string& file) {
const image::ImagePtr& img = image::loadImage(file, false);
if (!img->isLoaded()) {
return false;
}
uint32_t buf[256];
if (!voxel::createPalette(img, buf, 256)) {
return false;
}
if (!voxel::overrideMaterialColors((const uint8_t*)buf, sizeof(buf), "")) {
Log::warn("Failed to import palette for image %s", file.c_str());
return false;
}
return true;
}
bool SceneManager::importAsPlane(const std::string& file) {
const image::ImagePtr& img = image::loadImage(file, false);
if (!img->isLoaded()) {
@ -701,6 +717,14 @@ void SceneManager::construct() {
moveCursor(x, y, z);
}).setHelp("Move the cursor by the specified offsets");
core::Command::registerCommand("createpalette", [this] (const core::CmdArgs& args) {
if (args.size() != 1) {
Log::info("Expected to get an image file name as parameter");
return;
}
importPalette(args[0]);
});
core::Command::registerCommand("cursor", [this] (const core::CmdArgs& args) {
if (args.size() < 3) {
Log::info("Expected to get x, y and z coordinates");

View File

@ -189,6 +189,7 @@ public:
bool voxelizeModel(const video::MeshPtr& mesh);
bool importHeightmap(const std::string& file);
bool importAsPlane(const std::string& file);
bool importPalette(const std::string& file);
bool exportModel(const std::string& file);
bool save(const std::string& file, bool autosave = false);
bool load(const std::string& file);

View File

@ -0,0 +1,22 @@
/**
* @file
*/
#include "core/tests/AbstractTest.h"
#include "../tool/ImageUtils.h"
#include "voxel/MaterialColor.h"
namespace voxedit {
class ImageUtilsTest: public core::AbstractTest {
};
TEST_F(ImageUtilsTest, testCreateAndLoadPalette) {
const image::ImagePtr& img = image::loadImage("test-palette-in.png", false);
ASSERT_TRUE(img->isLoaded()) << "Failed to load image: " << img->name();
uint32_t buf[256];
EXPECT_TRUE(voxel::createPalette(img, buf, 256)) << "Failed to create palette image";
EXPECT_TRUE(voxel::overrideMaterialColors((const uint8_t*)buf, sizeof(buf), ""));
}
}

View File

@ -11,6 +11,7 @@
#include "core/Color.h"
#include "core/Log.h"
#include "core/GLM.h"
#include <unordered_set>
namespace voxedit {