Add controls to allow resizing image textures, and simplifying RGBA data
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
#include <wx/image.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/statline.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
@@ -2319,6 +2320,202 @@ static std::vector<uint8_t> sample_raw_offsets_bilinear_wrapped(const std::vecto
|
||||
return values;
|
||||
}
|
||||
|
||||
static bool checked_texture_buffer_size(uint32_t width, uint32_t height, uint32_t channels, size_t& size)
|
||||
{
|
||||
size = 0;
|
||||
if (width == 0 || height == 0 || channels == 0)
|
||||
return false;
|
||||
if (size_t(width) > std::numeric_limits<size_t>::max() / size_t(height))
|
||||
return false;
|
||||
const size_t pixels = size_t(width) * size_t(height);
|
||||
if (pixels > std::numeric_limits<size_t>::max() / size_t(channels))
|
||||
return false;
|
||||
size = pixels * size_t(channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> resize_rgba_texture_bilinear(
|
||||
const std::vector<uint8_t>& rgba, uint32_t width, uint32_t height, uint32_t resized_width, uint32_t resized_height)
|
||||
{
|
||||
size_t resized_size = 0;
|
||||
if (!checked_texture_buffer_size(resized_width, resized_height, 4, resized_size))
|
||||
return {};
|
||||
|
||||
std::vector<uint8_t> resized(resized_size, 255);
|
||||
size_t source_size = 0;
|
||||
if (!checked_texture_buffer_size(width, height, 4, source_size) || rgba.size() < source_size)
|
||||
return resized;
|
||||
|
||||
auto channel = [&rgba, width](size_t sx, size_t sy, size_t ch) { return float(rgba[(sy * size_t(width) + sx) * 4 + ch]) / 255.f; };
|
||||
for (uint32_t y = 0; y < resized_height; ++y) {
|
||||
const float source_y = std::clamp((float(y) + 0.5f) * float(height) / float(resized_height) - 0.5f, 0.f, float(height - 1));
|
||||
const size_t y0 = std::min<size_t>(size_t(std::floor(source_y)), size_t(height - 1));
|
||||
const size_t y1 = std::min<size_t>(y0 + 1, size_t(height - 1));
|
||||
const float ty = source_y - float(y0);
|
||||
for (uint32_t x = 0; x < resized_width; ++x) {
|
||||
const float source_x = std::clamp((float(x) + 0.5f) * float(width) / float(resized_width) - 0.5f, 0.f, float(width - 1));
|
||||
const size_t x0 = std::min<size_t>(size_t(std::floor(source_x)), size_t(width - 1));
|
||||
const size_t x1 = std::min<size_t>(x0 + 1, size_t(width - 1));
|
||||
const float tx = source_x - float(x0);
|
||||
auto blend_channel = [&](size_t ch) {
|
||||
const float c00 = channel(x0, y0, ch);
|
||||
const float c10 = channel(x1, y0, ch);
|
||||
const float c01 = channel(x0, y1, ch);
|
||||
const float c11 = channel(x1, y1, ch);
|
||||
const float cx0 = c00 + (c10 - c00) * tx;
|
||||
const float cx1 = c01 + (c11 - c01) * tx;
|
||||
return std::clamp(cx0 + (cx1 - cx0) * ty, 0.f, 1.f);
|
||||
};
|
||||
auto blend_premultiplied_channel = [&](size_t ch) {
|
||||
const float c00 = channel(x0, y0, ch) * channel(x0, y0, 3);
|
||||
const float c10 = channel(x1, y0, ch) * channel(x1, y0, 3);
|
||||
const float c01 = channel(x0, y1, ch) * channel(x0, y1, 3);
|
||||
const float c11 = channel(x1, y1, ch) * channel(x1, y1, 3);
|
||||
const float cx0 = c00 + (c10 - c00) * tx;
|
||||
const float cx1 = c01 + (c11 - c01) * tx;
|
||||
return std::clamp(cx0 + (cx1 - cx0) * ty, 0.f, 1.f);
|
||||
};
|
||||
const float a = blend_channel(3);
|
||||
const size_t idx = (size_t(y) * size_t(resized_width) + size_t(x)) * 4;
|
||||
resized[idx + 0] =
|
||||
uint8_t(std::clamp(a > 0.f ? blend_premultiplied_channel(0) / a : blend_channel(0), 0.f, 1.f) * 255.f + 0.5f);
|
||||
resized[idx + 1] =
|
||||
uint8_t(std::clamp(a > 0.f ? blend_premultiplied_channel(1) / a : blend_channel(1), 0.f, 1.f) * 255.f + 0.5f);
|
||||
resized[idx + 2] =
|
||||
uint8_t(std::clamp(a > 0.f ? blend_premultiplied_channel(2) / a : blend_channel(2), 0.f, 1.f) * 255.f + 0.5f);
|
||||
resized[idx + 3] = uint8_t(a * 255.f + 0.5f);
|
||||
}
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> resize_raw_offsets_bilinear(const std::vector<uint8_t>& offsets,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t channels,
|
||||
uint32_t resized_width,
|
||||
uint32_t resized_height)
|
||||
{
|
||||
size_t resized_size = 0;
|
||||
if (!checked_texture_buffer_size(resized_width, resized_height, channels, resized_size))
|
||||
return {};
|
||||
|
||||
std::vector<uint8_t> resized(resized_size, 0);
|
||||
size_t source_size = 0;
|
||||
if (!checked_texture_buffer_size(width, height, channels, source_size) || offsets.size() < source_size)
|
||||
return resized;
|
||||
|
||||
auto channel = [&offsets, width, channels](size_t sx, size_t sy, size_t ch) {
|
||||
return float(offsets[(sy * size_t(width) + sx) * size_t(channels) + ch]);
|
||||
};
|
||||
for (uint32_t y = 0; y < resized_height; ++y) {
|
||||
const float source_y = std::clamp((float(y) + 0.5f) * float(height) / float(resized_height) - 0.5f, 0.f, float(height - 1));
|
||||
const size_t y0 = std::min<size_t>(size_t(std::floor(source_y)), size_t(height - 1));
|
||||
const size_t y1 = std::min<size_t>(y0 + 1, size_t(height - 1));
|
||||
const float ty = source_y - float(y0);
|
||||
for (uint32_t x = 0; x < resized_width; ++x) {
|
||||
const float source_x = std::clamp((float(x) + 0.5f) * float(width) / float(resized_width) - 0.5f, 0.f, float(width - 1));
|
||||
const size_t x0 = std::min<size_t>(size_t(std::floor(source_x)), size_t(width - 1));
|
||||
const size_t x1 = std::min<size_t>(x0 + 1, size_t(width - 1));
|
||||
const float tx = source_x - float(x0);
|
||||
const size_t idx = (size_t(y) * size_t(resized_width) + size_t(x)) * size_t(channels);
|
||||
for (size_t ch = 0; ch < size_t(channels); ++ch) {
|
||||
const float c00 = channel(x0, y0, ch);
|
||||
const float c10 = channel(x1, y0, ch);
|
||||
const float c01 = channel(x0, y1, ch);
|
||||
const float c11 = channel(x1, y1, ch);
|
||||
const float cx0 = c00 + (c10 - c00) * tx;
|
||||
const float cx1 = c01 + (c11 - c01) * tx;
|
||||
resized[idx + ch] = uint8_t(std::clamp(cx0 + (cx1 - cx0) * ty, 0.f, 255.f) + 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
static std::string resized_raw_texture_metadata_json(const std::string& metadata_json, uint32_t width, uint32_t height, uint32_t channels)
|
||||
{
|
||||
nlohmann::json root;
|
||||
try {
|
||||
root = nlohmann::json::parse(metadata_json);
|
||||
if (!root.is_object())
|
||||
root = nlohmann::json::object();
|
||||
} catch (...) {
|
||||
root = nlohmann::json::object();
|
||||
}
|
||||
root["format"] = "raw_filament_offset_atlas";
|
||||
root["image"] = {{"width", width}, {"height", height}, {"channels", channels}};
|
||||
root.erase("regions");
|
||||
return root.dump();
|
||||
}
|
||||
|
||||
static bool resize_volume_image_texture(ModelVolume& volume, uint32_t resized_width, uint32_t resized_height)
|
||||
{
|
||||
if (!model_volume_has_imported_image_texture_data(&volume) || resized_width == 0 || resized_height == 0)
|
||||
return false;
|
||||
|
||||
const uint32_t old_width = volume.imported_texture_width;
|
||||
const uint32_t old_height = volume.imported_texture_height;
|
||||
if (old_width == resized_width && old_height == resized_height)
|
||||
return false;
|
||||
|
||||
const bool has_raw_atlas = model_volume_has_raw_atlas_texture_data(&volume);
|
||||
if (has_raw_atlas) {
|
||||
std::vector<uint8_t> resized_offsets = resize_raw_offsets_bilinear(volume.imported_texture_raw_filament_offsets, old_width,
|
||||
old_height, volume.imported_texture_raw_channels, resized_width,
|
||||
resized_height);
|
||||
if (resized_offsets.empty())
|
||||
return false;
|
||||
volume.imported_texture_raw_filament_offsets = std::move(resized_offsets);
|
||||
volume.imported_texture_width = resized_width;
|
||||
volume.imported_texture_height = resized_height;
|
||||
volume.imported_texture_raw_metadata_json = resized_raw_texture_metadata_json(volume.imported_texture_raw_metadata_json,
|
||||
resized_width, resized_height,
|
||||
volume.imported_texture_raw_channels);
|
||||
refresh_imported_texture_preview_from_raw_offsets(volume);
|
||||
volume.imported_texture_raw_filament_offsets.set_new_unique_id();
|
||||
volume.imported_texture_rgba.set_new_unique_id();
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t source_rgba_size = 0;
|
||||
if (!checked_texture_buffer_size(old_width, old_height, 4, source_rgba_size) || volume.imported_texture_rgba.size() < source_rgba_size)
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> resized_rgba = resize_rgba_texture_bilinear(volume.imported_texture_rgba, old_width, old_height, resized_width,
|
||||
resized_height);
|
||||
if (resized_rgba.empty())
|
||||
return false;
|
||||
|
||||
volume.imported_texture_rgba = std::move(resized_rgba);
|
||||
const bool cleared_raw_data = !volume.imported_texture_raw_filament_offsets.empty() || volume.imported_texture_raw_channels != 0 ||
|
||||
!volume.imported_texture_raw_metadata_json.empty();
|
||||
if (cleared_raw_data)
|
||||
clear_imported_texture_raw_atlas(volume);
|
||||
volume.imported_texture_width = resized_width;
|
||||
volume.imported_texture_height = resized_height;
|
||||
volume.imported_texture_rgba.set_new_unique_id();
|
||||
if (cleared_raw_data)
|
||||
volume.imported_texture_raw_filament_offsets.set_new_unique_id();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool resize_object_image_textures(ModelObject& object, double scale)
|
||||
{
|
||||
if (!std::isfinite(scale) || scale <= 0.0)
|
||||
return false;
|
||||
|
||||
bool changed = false;
|
||||
for (ModelVolume* volume : object.volumes) {
|
||||
if (volume == nullptr || !volume->is_model_part() || !model_volume_has_imported_image_texture_data(volume))
|
||||
continue;
|
||||
const uint32_t resized_width = std::max<uint32_t>(1, uint32_t(std::llround(double(volume->imported_texture_width) * scale)));
|
||||
const uint32_t resized_height = std::max<uint32_t>(1, uint32_t(std::llround(double(volume->imported_texture_height) * scale)));
|
||||
changed |= resize_volume_image_texture(*volume, resized_width, resized_height);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static ColorRGBA blend_projection_color(const ColorRGBA &base, const ColorRGBA &overlay, float opacity)
|
||||
{
|
||||
const float alpha = std::clamp(overlay.a(), 0.f, 1.f) * std::clamp(opacity, 0.f, 1.f);
|
||||
@@ -4410,7 +4607,7 @@ struct ManagedColorDataSummary
|
||||
size_t raw_offset_image_texture_count = 0;
|
||||
uint32_t max_texture_width = 0;
|
||||
uint32_t max_texture_height = 0;
|
||||
size_t rgba_data_bytes = 0;
|
||||
size_t rgba_data_triangle_count = 0;
|
||||
std::vector<std::string> raw_offset_color_modes;
|
||||
};
|
||||
|
||||
@@ -4508,15 +4705,6 @@ static wxString managed_color_data_type_label(ManagedColorDataType type)
|
||||
return wxString();
|
||||
}
|
||||
|
||||
static size_t estimated_rgba_data_bytes(const ColorFacetsAnnotation &annotation)
|
||||
{
|
||||
const TriangleColorSplittingData &data = annotation.get_data();
|
||||
return data.triangles_to_split.size() * sizeof(ColorTriangleBitStreamMapping) +
|
||||
(data.bitstream.size() + 7) / 8 +
|
||||
data.colors_rgba.size() * sizeof(uint32_t) +
|
||||
data.metadata_json.size();
|
||||
}
|
||||
|
||||
static ManagedColorDataSummary summarize_managed_color_data(const ModelObject *object)
|
||||
{
|
||||
ManagedColorDataSummary summary;
|
||||
@@ -4555,7 +4743,7 @@ static ManagedColorDataSummary summarize_managed_color_data(const ModelObject *o
|
||||
|
||||
if (!volume->texture_mapping_color_facets.empty()) {
|
||||
summary.has_rgba_data = true;
|
||||
summary.rgba_data_bytes += estimated_rgba_data_bytes(volume->texture_mapping_color_facets);
|
||||
summary.rgba_data_triangle_count += volume->texture_mapping_color_facets.get_data().colors_rgba.size();
|
||||
}
|
||||
}
|
||||
return summary;
|
||||
@@ -4580,9 +4768,7 @@ static wxString managed_color_data_size_text(const ManagedColorDataSummary &summ
|
||||
static_cast<unsigned>(summary.max_texture_width),
|
||||
static_cast<unsigned>(summary.max_texture_height));
|
||||
case ManagedColorDataType::RgbaData:
|
||||
if (summary.rgba_data_bytes > 0 && summary.rgba_data_bytes < 1024 * 1024 / 100)
|
||||
return _L("<0.01 MB");
|
||||
return wxString::Format(_L("%.2f MB"), double(summary.rgba_data_bytes) / (1024.0 * 1024.0));
|
||||
return wxString::Format(_L("%llu triangles"), static_cast<unsigned long long>(summary.rgba_data_triangle_count));
|
||||
}
|
||||
return wxString();
|
||||
}
|
||||
@@ -5113,6 +5299,364 @@ static void managed_color_data_append_rgba_hex(std::string &out, uint32_t rgba)
|
||||
out.push_back(managed_color_data_hex_digit((rgba >> shift) & 0x0Fu));
|
||||
}
|
||||
|
||||
struct ManagedColorDataRgbaAccumulator
|
||||
{
|
||||
double r = 0.0;
|
||||
double g = 0.0;
|
||||
double b = 0.0;
|
||||
double a = 0.0;
|
||||
double area_weight = 0.0;
|
||||
double count_r = 0.0;
|
||||
double count_g = 0.0;
|
||||
double count_b = 0.0;
|
||||
double count_a = 0.0;
|
||||
size_t count = 0;
|
||||
};
|
||||
|
||||
static double managed_color_data_triangle_area(const std::array<Vec3f, 3> &vertices)
|
||||
{
|
||||
const Vec3f edge0 = vertices[1] - vertices[0];
|
||||
const Vec3f edge1 = vertices[2] - vertices[0];
|
||||
const double area = 0.5 * double(edge0.cross(edge1).norm());
|
||||
return std::isfinite(area) && area > double(EPSILON) ? area : 0.0;
|
||||
}
|
||||
|
||||
static double managed_color_data_triangle_area(const ColorFacetTriangle &facet)
|
||||
{
|
||||
return managed_color_data_triangle_area(facet.vertices);
|
||||
}
|
||||
|
||||
static void managed_color_data_accumulate_rgba(ManagedColorDataRgbaAccumulator &accumulator, uint32_t rgba, double area)
|
||||
{
|
||||
const ColorRGBA color = unpack_vertex_color_rgba_for_conversion(rgba);
|
||||
if (area > 0.0) {
|
||||
accumulator.r += double(color.r()) * area;
|
||||
accumulator.g += double(color.g()) * area;
|
||||
accumulator.b += double(color.b()) * area;
|
||||
accumulator.a += double(color.a()) * area;
|
||||
accumulator.area_weight += area;
|
||||
}
|
||||
accumulator.count_r += double(color.r());
|
||||
accumulator.count_g += double(color.g());
|
||||
accumulator.count_b += double(color.b());
|
||||
accumulator.count_a += double(color.a());
|
||||
++accumulator.count;
|
||||
}
|
||||
|
||||
static uint32_t managed_color_data_averaged_rgba(const ManagedColorDataRgbaAccumulator &accumulator)
|
||||
{
|
||||
if (accumulator.area_weight > 0.0)
|
||||
return pack_vertex_color_rgba(ColorRGBA(float(accumulator.r / accumulator.area_weight),
|
||||
float(accumulator.g / accumulator.area_weight),
|
||||
float(accumulator.b / accumulator.area_weight),
|
||||
float(accumulator.a / accumulator.area_weight)));
|
||||
|
||||
const double count = std::max<double>(1.0, double(accumulator.count));
|
||||
return pack_vertex_color_rgba(ColorRGBA(float(accumulator.count_r / count),
|
||||
float(accumulator.count_g / count),
|
||||
float(accumulator.count_b / count),
|
||||
float(accumulator.count_a / count)));
|
||||
}
|
||||
|
||||
static void managed_color_data_merge_rgba_accumulator(ManagedColorDataRgbaAccumulator &target,
|
||||
const ManagedColorDataRgbaAccumulator &source)
|
||||
{
|
||||
target.r += source.r;
|
||||
target.g += source.g;
|
||||
target.b += source.b;
|
||||
target.a += source.a;
|
||||
target.area_weight += source.area_weight;
|
||||
target.count_r += source.count_r;
|
||||
target.count_g += source.count_g;
|
||||
target.count_b += source.count_b;
|
||||
target.count_a += source.count_a;
|
||||
target.count += source.count;
|
||||
}
|
||||
|
||||
static int managed_color_data_read_rgba_nibble(const TriangleColorSplittingData &data, int bitstream_end, int &bit_idx)
|
||||
{
|
||||
if (bit_idx < 0 || bit_idx + 4 > bitstream_end || size_t(bit_idx + 3) >= data.bitstream.size())
|
||||
return -1;
|
||||
|
||||
int code = 0;
|
||||
for (int bit = 0; bit < 4; ++bit)
|
||||
code |= int(data.bitstream[size_t(bit_idx++)]) << bit;
|
||||
return code;
|
||||
}
|
||||
|
||||
static std::array<std::array<Vec3f, 3>, 4> managed_color_data_split_rgba_triangle(const std::array<Vec3f, 3> &vertices,
|
||||
int split_sides,
|
||||
int special_side)
|
||||
{
|
||||
std::array<std::array<Vec3f, 3>, 4> children{};
|
||||
special_side = std::clamp(special_side, 0, 2);
|
||||
const int j = (special_side + 1) % 3;
|
||||
const int k = (special_side + 2) % 3;
|
||||
const Vec3f a = vertices[size_t(special_side)];
|
||||
const Vec3f b = vertices[size_t(j)];
|
||||
const Vec3f c = vertices[size_t(k)];
|
||||
const Vec3f ab = 0.5f * (a + b);
|
||||
const Vec3f bc = 0.5f * (b + c);
|
||||
const Vec3f ca = 0.5f * (c + a);
|
||||
|
||||
if (split_sides == 1) {
|
||||
children[0] = { a, b, bc };
|
||||
children[1] = { bc, c, a };
|
||||
} else if (split_sides == 2) {
|
||||
children[0] = { a, ab, ca };
|
||||
children[1] = { ab, b, ca };
|
||||
children[2] = { b, c, ca };
|
||||
} else {
|
||||
children[0] = { a, ab, ca };
|
||||
children[1] = { ab, b, bc };
|
||||
children[2] = { bc, c, ca };
|
||||
children[3] = { ab, bc, ca };
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
struct ManagedColorDataUnsplitResult
|
||||
{
|
||||
bool valid = false;
|
||||
bool input_leaf = false;
|
||||
bool changed = false;
|
||||
ManagedColorDataRgbaAccumulator accumulator;
|
||||
std::vector<bool> bits;
|
||||
std::vector<uint32_t> colors;
|
||||
};
|
||||
|
||||
static ManagedColorDataUnsplitResult managed_color_data_unsplit_rgba_node_once(const TriangleColorSplittingData &data,
|
||||
int bitstream_end,
|
||||
size_t color_end,
|
||||
int &bit_idx,
|
||||
size_t &color_idx,
|
||||
const std::array<Vec3f, 3> &vertices)
|
||||
{
|
||||
ManagedColorDataUnsplitResult result;
|
||||
const int code = managed_color_data_read_rgba_nibble(data, bitstream_end, bit_idx);
|
||||
if (code < 0)
|
||||
return result;
|
||||
|
||||
const int split_sides = code & 0b11;
|
||||
if (split_sides == 0) {
|
||||
if (color_idx >= color_end || color_idx >= data.colors_rgba.size())
|
||||
return result;
|
||||
|
||||
const uint32_t rgba = data.colors_rgba[color_idx++];
|
||||
managed_color_data_append_nibble(result.bits, 0u);
|
||||
result.colors.emplace_back(rgba);
|
||||
managed_color_data_accumulate_rgba(result.accumulator, rgba, managed_color_data_triangle_area(vertices));
|
||||
result.valid = true;
|
||||
result.input_leaf = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
const int special_side = (code >> 2) & 0b11;
|
||||
const std::array<std::array<Vec3f, 3>, 4> child_vertices =
|
||||
managed_color_data_split_rgba_triangle(vertices, split_sides, special_side);
|
||||
std::array<ManagedColorDataUnsplitResult, 4> child_results;
|
||||
bool all_children_are_leaves = true;
|
||||
bool any_child_changed = false;
|
||||
for (int child_idx = split_sides; child_idx >= 0; --child_idx) {
|
||||
child_results[size_t(child_idx)] = managed_color_data_unsplit_rgba_node_once(data,
|
||||
bitstream_end,
|
||||
color_end,
|
||||
bit_idx,
|
||||
color_idx,
|
||||
child_vertices[size_t(child_idx)]);
|
||||
const ManagedColorDataUnsplitResult &child = child_results[size_t(child_idx)];
|
||||
if (!child.valid)
|
||||
return ManagedColorDataUnsplitResult();
|
||||
all_children_are_leaves &= child.input_leaf;
|
||||
any_child_changed |= child.changed;
|
||||
managed_color_data_merge_rgba_accumulator(result.accumulator, child.accumulator);
|
||||
}
|
||||
|
||||
result.valid = true;
|
||||
if (all_children_are_leaves) {
|
||||
managed_color_data_append_nibble(result.bits, 0u);
|
||||
result.colors.emplace_back(managed_color_data_averaged_rgba(result.accumulator));
|
||||
result.changed = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
managed_color_data_append_nibble(result.bits, unsigned(code));
|
||||
for (int child_idx = split_sides; child_idx >= 0; --child_idx) {
|
||||
const ManagedColorDataUnsplitResult &child = child_results[size_t(child_idx)];
|
||||
result.bits.insert(result.bits.end(), child.bits.begin(), child.bits.end());
|
||||
result.colors.insert(result.colors.end(), child.colors.begin(), child.colors.end());
|
||||
}
|
||||
result.changed = any_child_changed;
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool rgba_data_has_split_triangles(const ColorFacetsAnnotation &annotation)
|
||||
{
|
||||
const std::vector<bool> &bitstream = annotation.get_data().bitstream;
|
||||
for (size_t bit_idx = 0; bit_idx + 3 < bitstream.size(); bit_idx += 4) {
|
||||
int code = 0;
|
||||
for (int bit = 0; bit < 4; ++bit)
|
||||
code |= int(bitstream[bit_idx + size_t(bit)]) << bit;
|
||||
if ((code & 0b11) != 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool object_has_splittable_rgba_data(const ModelObject &object)
|
||||
{
|
||||
for (const ModelVolume *volume : object.volumes)
|
||||
if (volume != nullptr && volume->is_model_part() && rgba_data_has_split_triangles(volume->texture_mapping_color_facets))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool unsplit_volume_rgba_data_once(ModelVolume &volume)
|
||||
{
|
||||
const TriangleColorSplittingData &data = volume.texture_mapping_color_facets.get_data();
|
||||
if (data.triangles_to_split.empty() || !rgba_data_has_split_triangles(volume.texture_mapping_color_facets))
|
||||
return false;
|
||||
|
||||
const indexed_triangle_set &its = volume.mesh().its;
|
||||
if (its.indices.empty())
|
||||
return false;
|
||||
|
||||
std::unique_ptr<ColorFacetsAnnotation> unsplit = ColorFacetsAnnotation::make_temporary();
|
||||
if (!unsplit)
|
||||
return false;
|
||||
|
||||
unsplit->set_metadata_json(volume.texture_mapping_color_facets.metadata_json());
|
||||
unsplit->reserve(int(data.triangles_to_split.size()));
|
||||
bool any = false;
|
||||
for (auto mapping_it = data.triangles_to_split.begin(); mapping_it != data.triangles_to_split.end(); ++mapping_it) {
|
||||
if (mapping_it->triangle_idx < 0 || size_t(mapping_it->triangle_idx) >= its.indices.size())
|
||||
continue;
|
||||
|
||||
const auto &tri = its.indices[size_t(mapping_it->triangle_idx)];
|
||||
if (tri[0] < 0 || tri[1] < 0 || tri[2] < 0)
|
||||
continue;
|
||||
if (size_t(tri[0]) >= its.vertices.size() ||
|
||||
size_t(tri[1]) >= its.vertices.size() ||
|
||||
size_t(tri[2]) >= its.vertices.size())
|
||||
continue;
|
||||
|
||||
const auto next_it = std::next(mapping_it);
|
||||
const int bitstream_end = next_it == data.triangles_to_split.end() ?
|
||||
int(data.bitstream.size()) :
|
||||
next_it->bitstream_start_idx;
|
||||
const size_t color_end = next_it == data.triangles_to_split.end() ?
|
||||
data.colors_rgba.size() :
|
||||
size_t(next_it->color_start_idx);
|
||||
if (mapping_it->bitstream_start_idx < 0 ||
|
||||
mapping_it->bitstream_start_idx >= bitstream_end ||
|
||||
size_t(bitstream_end) > data.bitstream.size() ||
|
||||
mapping_it->color_start_idx < 0 ||
|
||||
size_t(mapping_it->color_start_idx) >= color_end ||
|
||||
color_end > data.colors_rgba.size())
|
||||
continue;
|
||||
|
||||
const std::array<Vec3f, 3> vertices = {
|
||||
its.vertices[size_t(tri[0])].cast<float>(),
|
||||
its.vertices[size_t(tri[1])].cast<float>(),
|
||||
its.vertices[size_t(tri[2])].cast<float>()
|
||||
};
|
||||
int bit_idx = mapping_it->bitstream_start_idx;
|
||||
size_t color_idx = size_t(mapping_it->color_start_idx);
|
||||
ManagedColorDataUnsplitResult result =
|
||||
managed_color_data_unsplit_rgba_node_once(data, bitstream_end, color_end, bit_idx, color_idx, vertices);
|
||||
if (!result.valid || result.bits.empty() || result.colors.empty())
|
||||
continue;
|
||||
|
||||
std::string encoded;
|
||||
managed_color_data_bits_to_hex(result.bits, encoded);
|
||||
encoded.push_back('|');
|
||||
for (uint32_t rgba : result.colors)
|
||||
managed_color_data_append_rgba_hex(encoded, rgba);
|
||||
unsplit->set_triangle_from_string(mapping_it->triangle_idx, encoded);
|
||||
any = true;
|
||||
}
|
||||
if (!any)
|
||||
return false;
|
||||
|
||||
unsplit->shrink_to_fit();
|
||||
if (volume.texture_mapping_color_facets.equals(*unsplit))
|
||||
return false;
|
||||
|
||||
volume.texture_mapping_color_facets.assign(*unsplit);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool unsplit_object_rgba_data_once(ModelObject &object)
|
||||
{
|
||||
bool changed = false;
|
||||
for (ModelVolume *volume : object.volumes) {
|
||||
if (volume == nullptr || !volume->is_model_part())
|
||||
continue;
|
||||
changed |= unsplit_volume_rgba_data_once(*volume);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static bool simplify_volume_rgba_data_to_one_color_per_triangle(ModelVolume &volume)
|
||||
{
|
||||
if (volume.texture_mapping_color_facets.empty())
|
||||
return false;
|
||||
|
||||
const indexed_triangle_set &its = volume.mesh().its;
|
||||
if (its.indices.empty())
|
||||
return false;
|
||||
|
||||
std::vector<ColorFacetTriangle> facets;
|
||||
volume.texture_mapping_color_facets.get_facet_triangles(volume, facets);
|
||||
if (facets.empty())
|
||||
return false;
|
||||
|
||||
std::vector<ManagedColorDataRgbaAccumulator> accumulators(its.indices.size());
|
||||
for (const ColorFacetTriangle &facet : facets) {
|
||||
if (facet.source_triangle < 0 || size_t(facet.source_triangle) >= accumulators.size())
|
||||
continue;
|
||||
managed_color_data_accumulate_rgba(accumulators[size_t(facet.source_triangle)], facet.rgba, managed_color_data_triangle_area(facet));
|
||||
}
|
||||
|
||||
std::unique_ptr<ColorFacetsAnnotation> simplified = ColorFacetsAnnotation::make_temporary();
|
||||
if (!simplified)
|
||||
return false;
|
||||
|
||||
simplified->set_metadata_json(volume.texture_mapping_color_facets.metadata_json());
|
||||
simplified->reserve(int(its.indices.size()));
|
||||
bool any = false;
|
||||
for (size_t triangle_idx = 0; triangle_idx < accumulators.size(); ++triangle_idx) {
|
||||
if (accumulators[triangle_idx].count == 0)
|
||||
continue;
|
||||
|
||||
std::string encoded = "0|";
|
||||
managed_color_data_append_rgba_hex(encoded, managed_color_data_averaged_rgba(accumulators[triangle_idx]));
|
||||
simplified->set_triangle_from_string(int(triangle_idx), encoded);
|
||||
any = true;
|
||||
}
|
||||
if (!any)
|
||||
return false;
|
||||
|
||||
simplified->shrink_to_fit();
|
||||
if (volume.texture_mapping_color_facets.equals(*simplified))
|
||||
return false;
|
||||
|
||||
volume.texture_mapping_color_facets.assign(*simplified);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool simplify_object_rgba_data_to_one_color_per_triangle(ModelObject &object)
|
||||
{
|
||||
bool changed = false;
|
||||
for (ModelVolume *volume : object.volumes) {
|
||||
if (volume == nullptr || !volume->is_model_part())
|
||||
continue;
|
||||
changed |= simplify_volume_rgba_data_to_one_color_per_triangle(*volume);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
static ColorRGBA managed_color_data_state_color(const ManagedRegionColorSource &source, unsigned int state)
|
||||
{
|
||||
if (state < source.state_colors.size())
|
||||
@@ -6052,6 +6596,139 @@ static void refresh_managed_color_data_object(GLCanvas3D &parent, ModelObject *o
|
||||
parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
|
||||
class ResizeImageTextureDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
ResizeImageTextureDialog(wxWindow* parent, uint32_t width, uint32_t height)
|
||||
: wxDialog(parent, wxID_ANY, _L("Resize Texture"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
, m_original_width(width)
|
||||
, m_original_height(height)
|
||||
{
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY,
|
||||
wxString::Format(_L("Original size: %u x %u"), static_cast<unsigned>(m_original_width),
|
||||
static_cast<unsigned>(m_original_height))),
|
||||
0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(16));
|
||||
|
||||
wxFlexGridSizer* grid = new wxFlexGridSizer(2, FromDIP(8), FromDIP(8));
|
||||
grid->AddGrowableCol(1, 1);
|
||||
const uint32_t max_axis = std::min<uint32_t>(std::max({uint32_t(8192), m_original_width, m_original_height}),
|
||||
uint32_t(std::numeric_limits<int>::max()));
|
||||
const uint32_t original_max_axis = std::max(m_original_width, m_original_height);
|
||||
const double max_scale = original_max_axis > 0 ? double(max_axis) / double(original_max_axis) : 1.0;
|
||||
auto scaled_max = [max_scale](uint32_t value) {
|
||||
return std::min<uint32_t>(uint32_t(std::numeric_limits<int>::max()),
|
||||
std::max<uint32_t>(std::max<uint32_t>(1, value),
|
||||
uint32_t(std::floor(double(value) * max_scale))));
|
||||
};
|
||||
m_max_width = scaled_max(m_original_width);
|
||||
m_max_height = scaled_max(m_original_height);
|
||||
m_width_spin = new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(96), -1), wxSP_ARROW_KEYS, 1,
|
||||
int(m_max_width), int(std::min(m_original_width, m_max_width)));
|
||||
m_height_spin = new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(96), -1), wxSP_ARROW_KEYS, 1,
|
||||
int(m_max_height), int(std::min(m_original_height, m_max_height)));
|
||||
grid->Add(new wxStaticText(this, wxID_ANY, _L("Width")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(m_width_spin, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(new wxStaticText(this, wxID_ANY, _L("Height")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(m_height_spin, 0, wxALIGN_CENTER_VERTICAL);
|
||||
main_sizer->Add(grid, 0, wxEXPAND | wxALL, FromDIP(16));
|
||||
|
||||
m_scale_text = new wxStaticText(this, wxID_ANY, wxString());
|
||||
main_sizer->Add(m_scale_text, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(16));
|
||||
main_sizer->Add(new wxStaticLine(this), 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(16));
|
||||
|
||||
wxStdDialogButtonSizer* buttons = new wxStdDialogButtonSizer();
|
||||
m_ok_button = new wxButton(this, wxID_OK, _L("OK"));
|
||||
wxButton* cancel_button = new wxButton(this, wxID_CANCEL, _L("Cancel"));
|
||||
buttons->AddButton(m_ok_button);
|
||||
buttons->AddButton(cancel_button);
|
||||
buttons->Realize();
|
||||
main_sizer->Add(buttons, 0, wxEXPAND | wxALL, FromDIP(16));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
update_summary();
|
||||
Fit();
|
||||
SetMinSize(GetSize());
|
||||
CenterOnParent();
|
||||
|
||||
m_width_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent&) { update_from_width(); });
|
||||
m_width_spin->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update_from_width(); });
|
||||
m_height_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent&) { update_from_height(); });
|
||||
m_height_spin->Bind(wxEVT_TEXT, [this](wxCommandEvent&) { update_from_height(); });
|
||||
}
|
||||
|
||||
double scale() const { return m_scale; }
|
||||
|
||||
private:
|
||||
static uint32_t rounded_dimension(double value) { return std::max<uint32_t>(1, uint32_t(std::llround(value))); }
|
||||
|
||||
void set_spin_value(wxSpinCtrl* spin, uint32_t value, uint32_t max_value)
|
||||
{
|
||||
value = std::clamp<uint32_t>(value, 1, max_value);
|
||||
if (spin != nullptr && spin->GetValue() != int(value))
|
||||
spin->SetValue(int(value));
|
||||
}
|
||||
|
||||
void update_from_width()
|
||||
{
|
||||
if (m_updating || m_width_spin == nullptr || m_height_spin == nullptr || m_original_width == 0)
|
||||
return;
|
||||
|
||||
m_updating = true;
|
||||
const uint32_t width = std::clamp<uint32_t>(uint32_t(std::max(1, m_width_spin->GetValue())), 1, m_max_width);
|
||||
set_spin_value(m_width_spin, width, m_max_width);
|
||||
m_scale = double(width) / double(m_original_width);
|
||||
set_spin_value(m_height_spin, rounded_dimension(double(m_original_height) * m_scale), m_max_height);
|
||||
m_updating = false;
|
||||
update_summary();
|
||||
}
|
||||
|
||||
void update_from_height()
|
||||
{
|
||||
if (m_updating || m_width_spin == nullptr || m_height_spin == nullptr || m_original_height == 0)
|
||||
return;
|
||||
|
||||
m_updating = true;
|
||||
const uint32_t height = std::clamp<uint32_t>(uint32_t(std::max(1, m_height_spin->GetValue())), 1, m_max_height);
|
||||
set_spin_value(m_height_spin, height, m_max_height);
|
||||
m_scale = double(height) / double(m_original_height);
|
||||
set_spin_value(m_width_spin, rounded_dimension(double(m_original_width) * m_scale), m_max_width);
|
||||
m_updating = false;
|
||||
update_summary();
|
||||
}
|
||||
|
||||
void update_summary()
|
||||
{
|
||||
if (m_width_spin == nullptr || m_height_spin == nullptr)
|
||||
return;
|
||||
|
||||
const uint32_t width = std::clamp<uint32_t>(uint32_t(std::max(1, m_width_spin->GetValue())), 1, m_max_width);
|
||||
const uint32_t height = std::clamp<uint32_t>(uint32_t(std::max(1, m_height_spin->GetValue())), 1, m_max_height);
|
||||
const double dimension_pct = m_scale * 100.0;
|
||||
const double original_pixels = double(m_original_width) * double(m_original_height);
|
||||
const double resized_pixels = double(width) * double(height);
|
||||
const double pixel_pct = original_pixels > 0.0 ? resized_pixels * 100.0 / original_pixels : 100.0;
|
||||
if (m_scale_text != nullptr)
|
||||
m_scale_text->SetLabel(wxString::Format(_L("New size: %u x %u (%.1f%% dimensions, %.1f%% pixels)"),
|
||||
static_cast<unsigned>(width), static_cast<unsigned>(height), dimension_pct, pixel_pct));
|
||||
if (m_ok_button != nullptr)
|
||||
m_ok_button->Enable(width != m_original_width || height != m_original_height);
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
uint32_t m_original_width = 0;
|
||||
uint32_t m_original_height = 0;
|
||||
uint32_t m_max_width = 1;
|
||||
uint32_t m_max_height = 1;
|
||||
wxSpinCtrl* m_width_spin = nullptr;
|
||||
wxSpinCtrl* m_height_spin = nullptr;
|
||||
wxStaticText* m_scale_text = nullptr;
|
||||
wxButton* m_ok_button = nullptr;
|
||||
double m_scale = 1.0;
|
||||
bool m_updating = false;
|
||||
};
|
||||
|
||||
class ColorDataManagementDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
@@ -6157,9 +6834,9 @@ private:
|
||||
wxButton *actions = nullptr;
|
||||
wxBoxSizer *size_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
size_sizer->Add(size, 1, wxALIGN_CENTER_VERTICAL);
|
||||
if (type == ManagedColorDataType::ImageTexture) {
|
||||
if (type == ManagedColorDataType::ImageTexture || type == ManagedColorDataType::RgbaData) {
|
||||
actions = new wxButton(this, wxID_ANY, wxString::FromUTF8("\xE2\x96\xBE"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
|
||||
actions->SetToolTip(_L("Image texture actions"));
|
||||
actions->SetToolTip(type == ManagedColorDataType::ImageTexture ? _L("Image texture actions") : _L("RGBA data actions"));
|
||||
size_sizer->Add(actions, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
||||
}
|
||||
|
||||
@@ -6172,8 +6849,14 @@ private:
|
||||
|
||||
clear->Bind(wxEVT_BUTTON, [this, type](wxCommandEvent &) { clear_data(type); });
|
||||
create->Bind(wxEVT_BUTTON, [this, type, create](wxCommandEvent &) { show_create_menu(type, create); });
|
||||
if (actions != nullptr)
|
||||
actions->Bind(wxEVT_BUTTON, [this, actions](wxCommandEvent &) { show_image_texture_menu(actions); });
|
||||
if (actions != nullptr) {
|
||||
actions->Bind(wxEVT_BUTTON, [this, type, actions](wxCommandEvent &) {
|
||||
if (type == ManagedColorDataType::ImageTexture)
|
||||
show_image_texture_menu(actions);
|
||||
else if (type == ManagedColorDataType::RgbaData)
|
||||
show_rgba_data_menu(actions);
|
||||
});
|
||||
}
|
||||
m_rows.push_back({ type, status, preview, size, clear, create, actions });
|
||||
}
|
||||
|
||||
@@ -6285,16 +6968,64 @@ private:
|
||||
info->Enable(false);
|
||||
menu.AppendSeparator();
|
||||
|
||||
const int generate_id = wxWindow::NewControlId();
|
||||
wxMenuItem *generate = menu.Append(generate_id, _L("Generate new UV Map"));
|
||||
const int resize_id = wxWindow::NewControlId();
|
||||
wxMenuItem* resize = menu.Append(resize_id, _L("Resize Texture"));
|
||||
resize->Enable(m_object != nullptr && object_has_image_texture_data(*m_object));
|
||||
|
||||
const int generate_id = wxWindow::NewControlId();
|
||||
wxMenuItem* generate = menu.Append(generate_id, _L("Generate new UV Map"));
|
||||
generate->Enable(m_object != nullptr && object_has_regenerable_image_texture_uvs(*m_object));
|
||||
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, generate_id](wxCommandEvent &event) {
|
||||
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, resize_id, generate_id](wxCommandEvent& event) {
|
||||
if (event.GetId() == resize_id)
|
||||
resize_image_texture();
|
||||
if (event.GetId() == generate_id)
|
||||
generate_new_uv_map();
|
||||
});
|
||||
button->PopupMenu(&menu, wxPoint(0, button->GetSize().GetHeight()));
|
||||
}
|
||||
|
||||
void show_rgba_data_menu(wxButton *button)
|
||||
{
|
||||
if (button == nullptr)
|
||||
return;
|
||||
|
||||
wxMenu menu;
|
||||
const int unsplit_id = wxWindow::NewControlId();
|
||||
const int simplify_id = wxWindow::NewControlId();
|
||||
wxMenuItem *unsplit = menu.Append(unsplit_id, _L("Unsplit once"));
|
||||
wxMenuItem *simplify = menu.Append(simplify_id, _L("Simplify to 1 color per triangle"));
|
||||
unsplit->Enable(m_object != nullptr && object_has_splittable_rgba_data(*m_object));
|
||||
simplify->Enable(m_object != nullptr && object_has_rgba_data(*m_object));
|
||||
menu.Bind(wxEVT_COMMAND_MENU_SELECTED, [this, unsplit_id, simplify_id](wxCommandEvent &event) {
|
||||
if (event.GetId() == unsplit_id)
|
||||
unsplit_rgba_data_once();
|
||||
else if (event.GetId() == simplify_id)
|
||||
simplify_rgba_data_to_one_color_per_triangle();
|
||||
});
|
||||
button->PopupMenu(&menu, wxPoint(0, button->GetSize().GetHeight()));
|
||||
}
|
||||
|
||||
void resize_image_texture()
|
||||
{
|
||||
if (m_object == nullptr)
|
||||
return;
|
||||
|
||||
const ManagedColorDataSummary summary = summarize_managed_color_data(m_object);
|
||||
if (!summary.has_image_texture || summary.max_texture_width == 0 || summary.max_texture_height == 0)
|
||||
return;
|
||||
|
||||
ResizeImageTextureDialog dialog(this, summary.max_texture_width, summary.max_texture_height);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Resize image texture", UndoRedo::SnapshotType::GizmoAction);
|
||||
if (!resize_object_image_textures(*m_object, dialog.scale()))
|
||||
return;
|
||||
|
||||
refresh_object_after_change();
|
||||
refresh_rows();
|
||||
}
|
||||
|
||||
void generate_new_uv_map()
|
||||
{
|
||||
if (m_object == nullptr || !object_has_regenerable_image_texture_uvs(*m_object))
|
||||
@@ -6308,6 +7039,32 @@ private:
|
||||
refresh_rows();
|
||||
}
|
||||
|
||||
void unsplit_rgba_data_once()
|
||||
{
|
||||
if (m_object == nullptr || !object_has_splittable_rgba_data(*m_object))
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Unsplit RGBA data", UndoRedo::SnapshotType::GizmoAction);
|
||||
if (!unsplit_object_rgba_data_once(*m_object))
|
||||
return;
|
||||
|
||||
refresh_object_after_change();
|
||||
refresh_rows();
|
||||
}
|
||||
|
||||
void simplify_rgba_data_to_one_color_per_triangle()
|
||||
{
|
||||
if (m_object == nullptr || !object_has_rgba_data(*m_object))
|
||||
return;
|
||||
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "Simplify RGBA data", UndoRedo::SnapshotType::GizmoAction);
|
||||
if (!simplify_object_rgba_data_to_one_color_per_triangle(*m_object))
|
||||
return;
|
||||
|
||||
refresh_object_after_change();
|
||||
refresh_rows();
|
||||
}
|
||||
|
||||
void create_data(ManagedColorDataType type, const ManagedColorDataCreateSource &source)
|
||||
{
|
||||
if (m_object == nullptr || object_has_managed_color_data(*m_object, type) || (source.type && *source.type == type))
|
||||
|
||||
Reference in New Issue
Block a user