From e70984097761597686af9f9bec22c762da208dbb Mon Sep 17 00:00:00 2001 From: Oleksandra Yushchenko Date: Wed, 2 Feb 2022 13:22:39 +0100 Subject: [PATCH 01/22] DoubleSlider: Fixed draw of the ruler for sequential print (#7854) + Fixed a condition for detection of sequential print (may caused a crash from #7263) + suppress to show ruler ticks when step was not detected --- src/slic3r/GUI/DoubleSlider.cpp | 102 +++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index ee5aacb191..b5201d8618 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -1085,6 +1085,8 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do } long_step = step == 0 ? -1.0 : (double)step* std::pow(10, pow); + if (long_step < 0) + short_step = long_step; } void Control::draw_ruler(wxDC& dc) @@ -1103,44 +1105,76 @@ void Control::draw_ruler(wxDC& dc) wxColour old_clr = dc.GetTextForeground(); dc.SetTextForeground(GREY_PEN.GetColour()); - if (m_ruler.long_step < 0) - for (size_t tick = 1; tick < m_values.size(); tick++) { - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - draw_tick_text(dc, wxPoint(mid, pos), tick); + auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { + if (m_ruler.short_step <= 0.0) + return; + while (current_tick < max_tick) { + wxCoord pos = get_position_from_value(lround(current_tick)); + draw_ticks_pair(dc, pos, mid, 2); + current_tick += m_ruler.short_step; + if (current_tick > m_max_value) + break; } - else { - auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { - while (current_tick < max_tick) { - wxCoord pos = get_position_from_value(lround(current_tick)); - draw_ticks_pair(dc, pos, mid, 2); - current_tick += m_ruler.short_step; - if (current_tick > m_max_value) + }; + + double short_tick = std::nan(""); + int tick = 0; + double value = 0.0; + size_t sequence = 0; + int prev_y_pos = -1; + wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; + int values_size = (int)m_values.size(); + + if (m_ruler.long_step < 0) { + // sequential print when long_step wasn't detected because of a lot of printed objects + if (m_ruler.max_values.size() > 1) { + while (tick <= m_max_value && sequence < m_ruler.count()) { + // draw just ticks with max value + value = m_ruler.max_values[sequence]; + short_tick = tick; + + for (; tick < values_size; tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_max_value) break; + + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + if (prev_y_pos < 0 || prev_y_pos - pos >= label_height) { + draw_tick_text(dc, wxPoint(mid, pos), tick); + prev_y_pos = pos; + } + draw_short_ticks(dc, short_tick, tick); + + sequence++; + tick++; } - }; - - double short_tick = std::nan(""); - int tick = 0; - double value = 0.0; - size_t sequence = 0; - - int prev_y_pos = -1; - wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; - int values_size = (int)m_values.size(); - + } + // very short object or some non-trivial ruler with non-regular step (see https://github.com/prusa3d/PrusaSlicer/issues/7263) + else { + if (get_scroll_step() < 1) // step less then 1 px indicates very tall object with non-regular laayer step (probably in vase mode) + return; + for (size_t tick = 1; tick < m_values.size(); tick++) { + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + draw_tick_text(dc, wxPoint(mid, pos), tick); + } + } + } + else { while (tick <= m_max_value) { value += m_ruler.long_step; - if (value > m_ruler.max_values[sequence] && sequence < m_ruler.count()) { - value = m_ruler.long_step; - for (; tick < values_size; tick++) - if (m_values[tick] < value) - break; - // short ticks from the last tick to the end of current sequence - assert(! std::isnan(short_tick)); - draw_short_ticks(dc, short_tick, tick); - sequence++; - } + + if (sequence < m_ruler.count() && value > m_ruler.max_values[sequence]) + value = m_ruler.max_values[sequence]; + short_tick = tick; for (; tick < values_size; tick++) { @@ -1164,7 +1198,7 @@ void Control::draw_ruler(wxDC& dc) draw_short_ticks(dc, short_tick, tick); - if (value == m_ruler.max_values[sequence] && sequence < m_ruler.count()) { + if (sequence < m_ruler.count() && value == m_ruler.max_values[sequence]) { value = 0.0; sequence++; tick++; From 7d8749077743a9c197726539d8eb6a5a036e5e41 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 2 Feb 2022 14:25:36 +0100 Subject: [PATCH 02/22] Tech ENABLE_RELOAD_FROM_DISK_REWORK - A bunch of bug fixes in Reload from disk command: 1) Lost orientation after reload from disk (SPE-1182) 2) Wrong objects replacement from reload from disk command (SPE-1183) 3) Reload from disk not disabled for built-in models (SPE-1184) --- src/libslic3r/Format/3mf.cpp | 64 +++++++--- src/libslic3r/Format/AMF.cpp | 88 ++++++++------ src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 8 +- src/slic3r/GUI/Plater.cpp | 187 +++++++++++++++++++++++++++++- 5 files changed, 287 insertions(+), 62 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 0e44cc14ea..ad428a7357 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -121,18 +121,21 @@ static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; static constexpr const char* OBJECT_TYPE = "object"; static constexpr const char* VOLUME_TYPE = "volume"; -static constexpr const char* NAME_KEY = "name"; -static constexpr const char* MODIFIER_KEY = "modifier"; +static constexpr const char* NAME_KEY = "name"; +static constexpr const char* MODIFIER_KEY = "modifier"; static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; -static constexpr const char* MATRIX_KEY = "matrix"; -static constexpr const char* SOURCE_FILE_KEY = "source_file"; -static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; -static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; -static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; -static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; -static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; -static constexpr const char* SOURCE_IN_INCHES = "source_in_inches"; -static constexpr const char* SOURCE_IN_METERS = "source_in_meters"; +static constexpr const char* MATRIX_KEY = "matrix"; +static constexpr const char* SOURCE_FILE_KEY = "source_file"; +static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; +static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; +static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; +static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; +static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; +static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; +static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; +#if ENABLE_RELOAD_FROM_DISK_REWORK +static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; @@ -816,6 +819,20 @@ namespace Slic3r { return false; } +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { + ModelVolume* v = o->volumes[vol_id]; + if (v->source.input_file.empty()) + v->source.input_file = v->name.empty() ? filename : v->name; + if (v->source.volume_idx == -1) + v->source.volume_idx = vol_id; + if (v->source.object_idx == -1) + v->source.object_idx = obj_id; + } + } +#else int object_idx = 0; for (ModelObject* o : model.objects) { int volume_idx = 0; @@ -831,6 +848,7 @@ namespace Slic3r { } ++object_idx; } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK // // fixes the min z of the model if negative // model.adjust_min_z(); @@ -2052,15 +2070,19 @@ namespace Slic3r { else if (metadata.key == SOURCE_VOLUME_ID_KEY) volume->source.volume_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_X_KEY) - volume->source.mesh_offset(0) = ::atof(metadata.value.c_str()); + volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Y_KEY) - volume->source.mesh_offset(1) = ::atof(metadata.value.c_str()); + volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Z_KEY) - volume->source.mesh_offset(2) = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_IN_INCHES) + volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_IN_INCHES_KEY) volume->source.is_converted_from_inches = metadata.value == "1"; - else if (metadata.key == SOURCE_IN_METERS) + else if (metadata.key == SOURCE_IN_METERS_KEY) volume->source.is_converted_from_meters = metadata.value == "1"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) + volume->source.is_from_builtin_objects = metadata.value == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK else volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } @@ -2981,7 +3003,7 @@ namespace Slic3r { // stores volume's local matrix stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; - Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); + const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { stream << matrix(r, c); @@ -3005,9 +3027,13 @@ namespace Slic3r { } assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) - stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; else if (volume->source.is_converted_from_meters) - stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } // stores volume's config data diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a8b66b15b1..f2a902758e 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -657,11 +657,16 @@ void AMFParserContext::endElement(const char * /* name */) if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform) m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); - if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) { +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (m_volume->source.input_file.empty()) { +#else + if (m_volume->source.input_file.empty() && m_volume->type() == ModelVolumeType::MODEL_PART) { +#endif // ENABLE_RELOAD_FROM_DISK_REWORK m_volume->source.object_idx = (int)m_model.objects.size() - 1; m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; m_volume->center_geometry_after_creation(); - } else + } + else // pass false if the mesh offset has been already taken from the data m_volume->center_geometry_after_creation(m_volume->source.input_file.empty()); @@ -792,46 +797,44 @@ void AMFParserContext::endElement(const char * /* name */) // Is this volume a modifier volume? // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag. m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART); - } else if (strcmp(opt_key, "volume_type") == 0) { + } + else if (strcmp(opt_key, "volume_type") == 0) m_volume->set_type(ModelVolume::type_from_string(m_value[1])); - } - else if (strcmp(opt_key, "matrix") == 0) { + else if (strcmp(opt_key, "matrix") == 0) m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]); - } - else if (strcmp(opt_key, "source_file") == 0) { + else if (strcmp(opt_key, "source_file") == 0) m_volume->source.input_file = m_value[1]; - } - else if (strcmp(opt_key, "source_object_id") == 0) { + else if (strcmp(opt_key, "source_object_id") == 0) m_volume->source.object_idx = ::atoi(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_volume_id") == 0) { + else if (strcmp(opt_key, "source_volume_id") == 0) m_volume->source.volume_idx = ::atoi(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_x") == 0) { - m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_y") == 0) { - m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_offset_z") == 0) { - m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str()); - } - else if (strcmp(opt_key, "source_in_inches") == 0) { + else if (strcmp(opt_key, "source_offset_x") == 0) + m_volume->source.mesh_offset.x() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_offset_y") == 0) + m_volume->source.mesh_offset.y() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_offset_z") == 0) + m_volume->source.mesh_offset.z() = ::atof(m_value[1].c_str()); + else if (strcmp(opt_key, "source_in_inches") == 0) m_volume->source.is_converted_from_inches = m_value[1] == "1"; - } - else if (strcmp(opt_key, "source_in_meters") == 0) { + else if (strcmp(opt_key, "source_in_meters") == 0) m_volume->source.is_converted_from_meters = m_value[1] == "1"; - } +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (strcmp(opt_key, "source_is_builtin_volume") == 0) + m_volume->source.is_from_builtin_objects = m_value[1] == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } - } else if (m_path.size() == 3) { + } + else if (m_path.size() == 3) { if (m_path[1] == NODE_TYPE_MATERIAL) { if (m_material) m_material->attributes[m_value[0]] = m_value[1]; - } else if (m_path[1] == NODE_TYPE_OBJECT) { + } + else if (m_path[1] == NODE_TYPE_OBJECT) { if (m_object && m_value[0] == "name") m_object->name = std::move(m_value[1]); } - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) { if (m_volume && m_value[0] == "name") m_volume->name = std::move(m_value[1]); } @@ -919,7 +922,11 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitut unsigned int counter = 0; for (ModelVolume* v : o->volumes) { ++counter; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (v->source.input_file.empty()) +#else if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) +#endif // ENABLE_RELOAD_FROM_DISK_REWORK v->source.input_file = path; if (v->name.empty()) { v->name = o->name; @@ -1068,7 +1075,11 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubsti for (ModelObject *o : model->objects) for (ModelVolume *v : o->volumes) - if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART)) +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (v->source.input_file.empty()) +#else + if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) +#endif // ENABLE_RELOAD_FROM_DISK_REWORK v->source.input_file = path; return true; @@ -1237,18 +1248,15 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " "; const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix(); stream << std::setprecision(std::numeric_limits::max_digits10); - for (int r = 0; r < 4; ++r) - { - for (int c = 0; c < 4; ++c) - { + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { stream << matrix(r, c); - if ((r != 3) || (c != 3)) + if (r != 3 || c != 3) stream << " "; } } stream << "\n"; - if (!volume->source.input_file.empty()) - { + if (!volume->source.input_file.empty()) { std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); stream << " " << input_file << "\n"; stream << " " << volume->source.object_idx << "\n"; @@ -1262,12 +1270,16 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, stream << " 1\n"; else if (volume->source.is_converted_from_meters) stream << " 1\n"; - stream << std::setprecision(std::numeric_limits::max_digits10); +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << " 1\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + stream << std::setprecision(std::numeric_limits::max_digits10); const indexed_triangle_set &its = volume->mesh().its; for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << its.indices[i][j] + vertices_offset << "\n"; + stream << " " << its.indices[i][j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 52c9e10d3c..da55ae4ff4 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -70,6 +70,8 @@ #define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable show non-manifold edges #define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) +// Enable rework of Reload from disk command +#define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2b551eaca1..27c374b3f8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1691,16 +1691,16 @@ void ObjectList::load_shape_object(const std::string& type_name) if (selection.get_object_idx() != -1) return; - const int obj_idx = m_objects->size(); - if (obj_idx < 0) - return; - take_snapshot(_L("Add Shape")); // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (!m_objects->empty()) + m_objects->back()->volumes.front()->source.is_from_builtin_objects = true; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK wxGetApp().mainframe->update_title(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a6447745cb..ecdd857e1c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3531,8 +3531,45 @@ void Plater::priv::replace_with_stl() } } +#if ENABLE_RELOAD_FROM_DISK_REWORK +static std::vector> reloadable_volumes(const Model& model, const Selection& selection) +{ + std::vector> ret; + const std::set& selected_volumes_idxs = selection.get_volume_idxs(); + for (unsigned int idx : selected_volumes_idxs) { + const GLVolume& v = *selection.get_volume(idx); + const int o_idx = v.object_idx(); + if (0 <= o_idx && o_idx < int(model.objects.size())) { + const ModelObject* obj = model.objects[o_idx]; + const int v_idx = v.volume_idx(); + if (0 <= v_idx && v_idx < int(obj->volumes.size())) { + const ModelVolume* vol = obj->volumes[v_idx]; + if (!vol->source.is_from_builtin_objects && !vol->source.input_file.empty() && + vol->source.input_file != obj->input_file && !fs::path(vol->source.input_file).extension().string().empty()) + ret.push_back({ o_idx, v_idx }); + } + } + } + return ret; +} +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + void Plater::priv::reload_from_disk() { +#if ENABLE_RELOAD_FROM_DISK_REWORK + // collect selected reloadable ModelVolumes + std::vector> selected_volumes = reloadable_volumes(model, get_selection()); + + // nothing to reload, return + if (selected_volumes.empty()) + return; + + std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second); + }); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end()); +#else Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); const Selection& selection = get_selection(); @@ -3565,10 +3602,36 @@ void Plater::priv::reload_from_disk() } std::sort(selected_volumes.begin(), selected_volumes.end()); selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK // collects paths of files to load std::vector input_paths; std::vector missing_input_paths; +#if ENABLE_RELOAD_FROM_DISK_REWORK + std::vector> replace_paths; + for (auto [obj_idx, vol_idx] : selected_volumes) { + const ModelObject* object = model.objects[obj_idx]; + const ModelVolume* volume = object->volumes[vol_idx]; + if (fs::exists(volume->source.input_file)) + input_paths.push_back(volume->source.input_file); + else { + // searches the source in the same folder containing the object + bool found = false; + if (!object->input_file.empty()) { + fs::path object_path = fs::path(object->input_file).remove_filename(); + if (!object_path.empty()) { + object_path /= fs::path(volume->source.input_file).filename(); + if (fs::exists(object_path)) { + input_paths.push_back(object_path); + found = true; + } + } + } + if (!found) + missing_input_paths.push_back(volume->source.input_file); + } + } +#else std::vector replace_paths; for (const SelectedVolume& v : selected_volumes) { const ModelObject* object = model.objects[v.object_idx]; @@ -3598,6 +3661,7 @@ void Plater::priv::reload_from_disk() else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects) missing_input_paths.push_back(volume->name); } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK std::sort(missing_input_paths.begin(), missing_input_paths.end()); missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end()); @@ -3641,7 +3705,11 @@ void Plater::priv::reload_from_disk() //wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); MessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() == wxID_YES) - replace_paths.push_back(sel_filename_path); +#if ENABLE_RELOAD_FROM_DISK_REWORK + replace_paths.emplace_back(search, sel_filename_path); +#else + replace_paths.emplace_back(sel_filename_path); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK missing_input_paths.pop_back(); } } @@ -3652,6 +3720,10 @@ void Plater::priv::reload_from_disk() std::sort(replace_paths.begin(), replace_paths.end()); replace_paths.erase(std::unique(replace_paths.begin(), replace_paths.end()), replace_paths.end()); +#if ENABLE_RELOAD_FROM_DISK_REWORK + Plater::TakeSnapshot snapshot(q, _L("Reload from disk")); +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + std::vector fail_list; Busy busy(_L("Reload from:"), q->get_current_canvas3D()->get_wxglcanvas()); @@ -3680,6 +3752,86 @@ void Plater::priv::reload_from_disk() } // update the selected volumes whose source is the current file +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (auto [obj_idx, vol_idx] : selected_volumes) { + ModelObject* old_model_object = model.objects[obj_idx]; + ModelVolume* old_volume = old_model_object->volumes[vol_idx]; + + bool sinking = old_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD; + + bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); + bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); + if (has_source || has_name) { + int new_volume_idx = -1; + int new_object_idx = -1; + bool match_found = false; + // take idxs from the matching volume + if (has_source && old_volume->source.object_idx < int(new_model.objects.size())) { + const ModelObject* obj = new_model.objects[old_volume->source.object_idx]; + if (old_volume->source.volume_idx < int(obj->volumes.size())) { + if (obj->volumes[old_volume->source.volume_idx]->name == old_volume->name) { + new_volume_idx = old_volume->source.volume_idx; + new_object_idx = old_volume->source.object_idx; + match_found = true; + } + } + } + + if (!match_found && has_name) { + // take idxs from the 1st matching volume + for (size_t o = 0; o < new_model.objects.size(); ++o) { + ModelObject* obj = new_model.objects[o]; + bool found = false; + for (size_t v = 0; v < obj->volumes.size(); ++v) { + if (obj->volumes[v]->name == old_volume->name) { + new_volume_idx = (int)v; + new_object_idx = (int)o; + found = true; + break; + } + } + if (found) + break; + } + } + + if (new_object_idx < 0 || int(new_model.objects.size()) <= new_object_idx) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + continue; + } + ModelObject* new_model_object = new_model.objects[new_object_idx]; + if (new_volume_idx < 0 || int(new_model_object->volumes.size()) <= new_volume_idx) { + fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); + continue; + } + + old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); + ModelVolume* new_volume = old_model_object->volumes.back(); + new_volume->set_new_unique_id(); + new_volume->config.apply(old_volume->config); + new_volume->set_type(old_volume->type()); + new_volume->set_material_id(old_volume->material_id()); + new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + old_volume->get_transformation().get_matrix(true) * + old_volume->source.transform.get_matrix(true)); + new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); + new_volume->source.object_idx = old_volume->source.object_idx; + new_volume->source.volume_idx = old_volume->source.volume_idx; + assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); + if (old_volume->source.is_converted_from_inches) + new_volume->convert_from_imperial_units(); + else if (old_volume->source.is_converted_from_meters) + new_volume->convert_from_meters(); + std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back()); + old_model_object->delete_volume(old_model_object->volumes.size() - 1); + if (!sinking) + old_model_object->ensure_on_bed(); + old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1"); + + sla::reproject_points_and_holes(old_model_object); + } + } +#else for (const SelectedVolume& sel_v : selected_volumes) { ModelObject* old_model_object = model.objects[sel_v.object_idx]; ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; @@ -3756,10 +3908,19 @@ void Plater::priv::reload_from_disk() sla::reproject_points_and_holes(old_model_object); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK } busy.reset(); +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (auto [src, dest] : replace_paths) { + for (auto [obj_idx, vol_idx] : selected_volumes) { + if (boost::algorithm::iequals(model.objects[obj_idx]->volumes[vol_idx]->source.input_file, src.string())) + replace_volume_with_stl(obj_idx, vol_idx, dest, ""); + } + } +#else for (size_t i = 0; i < replace_paths.size(); ++i) { const auto& path = replace_paths[i].string(); for (const SelectedVolume& sel_v : selected_volumes) { @@ -3769,6 +3930,7 @@ void Plater::priv::reload_from_disk() replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, ""); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK if (!fail_list.empty()) { wxString message = _L("Unable to reload:") + "\n"; @@ -4556,6 +4718,13 @@ bool Plater::priv::can_replace_with_stl() const bool Plater::priv::can_reload_from_disk() const { +#if ENABLE_RELOAD_FROM_DISK_REWORK + // collect selected reloadable ModelVolumes + std::vector> selected_volumes = reloadable_volumes(model, get_selection()); + // nothing to reload, return + if (selected_volumes.empty()) + return false; +#else // struct to hold selected ModelVolumes by their indices struct SelectedVolume { @@ -4581,6 +4750,21 @@ bool Plater::priv::can_reload_from_disk() const selected_volumes.push_back({ o_idx, v_idx }); } } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +#if ENABLE_RELOAD_FROM_DISK_REWORK + std::sort(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first < v2.first) || (v1.first == v2.first && v1.second < v2.second); + }); + selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end(), [](const std::pair& v1, const std::pair& v2) { + return (v1.first == v2.first) && (v1.second == v2.second); }), selected_volumes.end()); + + // collects paths of files to load + std::vector paths; + for (auto [obj_idx, vol_idx] : selected_volumes) { + paths.push_back(model.objects[obj_idx]->volumes[vol_idx]->source.input_file); + } +#else std::sort(selected_volumes.begin(), selected_volumes.end()); selected_volumes.erase(std::unique(selected_volumes.begin(), selected_volumes.end()), selected_volumes.end()); @@ -4594,6 +4778,7 @@ bool Plater::priv::can_reload_from_disk() const else if (!object->input_file.empty() && !volume->name.empty() && !volume->source.is_from_builtin_objects) paths.push_back(volume->name); } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK std::sort(paths.begin(), paths.end()); paths.erase(std::unique(paths.begin(), paths.end()), paths.end()); From 81b9997009dff288984caa68e06e95bb1000bc62 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 2 Feb 2022 14:59:21 +0100 Subject: [PATCH 03/22] Follow-up of 7d8749077743a9c197726539d8eb6a5a036e5e41 - Fix into function reloadable_volumes() --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ecdd857e1c..54232ca874 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3545,7 +3545,7 @@ static std::vector> reloadable_volumes(const Model& model, c if (0 <= v_idx && v_idx < int(obj->volumes.size())) { const ModelVolume* vol = obj->volumes[v_idx]; if (!vol->source.is_from_builtin_objects && !vol->source.input_file.empty() && - vol->source.input_file != obj->input_file && !fs::path(vol->source.input_file).extension().string().empty()) + !fs::path(vol->source.input_file).extension().string().empty()) ret.push_back({ o_idx, v_idx }); } } From 222e3ec6ef5b6ffdb9b92c4cbfd95e6fe84d118f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 2 Feb 2022 15:25:35 +0100 Subject: [PATCH 04/22] Follow-up of cfe8aa48189556156b4799fd1bcb5b51c4ab3c91 - Fixed focus when moving between object manipulator fields by tab key or mouse click --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 4c1615dd46..98012fa2dd 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1104,8 +1104,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, parent->set_focused_editor(nullptr); #if ENABLE_OBJECT_MANIPULATOR_FOCUS - // if the widget loosing focus is not a manipulator field, call kill_focus - if (dynamic_cast(e.GetWindow()) == nullptr) + // if the widgets exchanging focus are both manipulator fields, call kill_focus + if (dynamic_cast(e.GetEventObject()) != nullptr && dynamic_cast(e.GetWindow()) != nullptr) #else if (!m_enter_pressed) #endif // ENABLE_OBJECT_MANIPULATOR_FOCUS From 87cff55856ae14096cd57f8e2542f1f1caef7167 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 2 Feb 2022 17:37:29 +0100 Subject: [PATCH 05/22] WIP: Implemented support for QOI G-code thumbnail format as requested by the RepRapFirmware team due to their low RAM budget for decompression. Bundled the QOI image compression library. --- src/CMakeLists.txt | 1 + src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/GCode.cpp | 50 +-- src/libslic3r/GCode/Thumbnails.cpp | 71 +++ src/libslic3r/GCode/Thumbnails.hpp | 58 +++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 41 +- src/libslic3r/PrintConfig.hpp | 5 + src/qoi/CMakeLists.txt | 9 + src/qoi/README.md | 108 +++++ src/qoi/qoi.h | 671 +++++++++++++++++++++++++++++ src/qoi/qoilib.c | 6 + src/slic3r/GUI/Tab.cpp | 1 + 14 files changed, 969 insertions(+), 60 deletions(-) create mode 100644 src/libslic3r/GCode/Thumbnails.cpp create mode 100644 src/libslic3r/GCode/Thumbnails.hpp create mode 100644 src/qoi/CMakeLists.txt create mode 100644 src/qoi/README.md create mode 100644 src/qoi/qoi.h create mode 100644 src/qoi/qoilib.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e89e82f67..ab1c7b9645 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(Shiny) add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) +add_subdirectory(qoi) # Adding libnest2d project for bin packing... add_subdirectory(libnest2d) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 39bc1aa474..d751969d56 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -98,6 +98,8 @@ set(SLIC3R_SOURCES Format/SL1.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp + GCode/Thumbnails.cpp + GCode/Thumbnails.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/FindReplace.cpp @@ -362,6 +364,7 @@ target_link_libraries(libslic3r ${CMAKE_DL_LIBS} PNG::PNG ZLIB::ZLIB + qoi ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5fac4b8229..ba51196ddc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6,6 +6,7 @@ #include "EdgeGrid.hpp" #include "Geometry/ConvexHull.hpp" #include "GCode/PrintExtents.hpp" +#include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" #include "ShortestPath.hpp" #include "Print.hpp" @@ -54,8 +55,6 @@ #include -#include "miniz_extension.hpp" - using namespace std::literals::string_view_literals; #if 0 @@ -937,49 +936,6 @@ namespace DoExport { } } - template - static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) - { - // Write thumbnails using base64 encoding - if (thumbnail_cb != nullptr) - { - const size_t max_row_length = 78; - ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - { - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) - { - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); - encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); - - output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); - - unsigned int row_count = 0; - while (encoded.size() > max_row_length) - { - output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); - encoded = encoded.substr(max_row_length); - ++row_count; - } - - if (encoded.size() > 0) - output((boost::format("; %s\n") % encoded).str().c_str()); - - output("; thumbnail end\n;\n"); - - mz_free(png_data); - } - } - throw_if_canceled(); - } - } - } - // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. static std::string update_print_stats_and_format_filament_stats( const bool has_wipe_tower, @@ -1163,7 +1119,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Write information on the generator. file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, + GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, + print.full_print_config().option("thumbnails")->values, + print.full_print_config().opt_enum("thumbnails_format"), [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); }); diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp new file mode 100644 index 0000000000..d04d3edcd7 --- /dev/null +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -0,0 +1,71 @@ +#include "Thumbnails.hpp" +#include "../miniz_extension.hpp" + +#include + +namespace Slic3r::GCodeThumbnails { + +using namespace std::literals; + +struct CompressedPNG : CompressedImageBuffer +{ + ~CompressedPNG() override { if (data) mz_free(data); } + std::string_view tag() const override { return "thumbnail"sv; } +}; + +struct CompressedQOI : CompressedImageBuffer +{ + ~CompressedQOI() override { free(data); } + std::string_view tag() const override { return "thumbnail_OQI"sv; } +}; + +std::unique_ptr compress_thumbnail_png(const ThumbnailData &data) +{ + auto out = std::make_unique(); + out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1); + return out; +} + +std::unique_ptr compress_thumbnail_jpg(const ThumbnailData& data) +{ + //FIXME change to JPG + auto out = std::make_unique(); + out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1); + return out; +} + +std::unique_ptr compress_thumbnail_qoi(const ThumbnailData &data) +{ + qoi_desc desc; + desc.width = data.width; + desc.height = data.height; + desc.channels = 4; + desc.colorspace = QOI_SRGB; + + // Take vector of RGBA pixels and flip the image vertically + std::vector rgba_pixels(data.pixels.size() * 4); + size_t row_size = data.width * 4; + for (size_t y = 0; y < data.height; ++ y) + memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + + auto out = std::make_unique(); + int size; + out->data = qoi_encode((const void*)rgba_pixels.data(), &desc, &size); + out->size = size; + return out; +} + +std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format) +{ + switch (format) { + case GCodeThumbnailsFormat::PNG: + default: + return compress_thumbnail_png(data); + case GCodeThumbnailsFormat::JPG: + return compress_thumbnail_jpg(data); + case GCodeThumbnailsFormat::QOI: + return compress_thumbnail_qoi(data); + } +} + +} // namespace Slic3r::GCodeThumbnails diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp new file mode 100644 index 0000000000..fe7ab69c51 --- /dev/null +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -0,0 +1,58 @@ +#ifndef slic3r_GCodeThumbnails_hpp_ +#define slic3r_GCodeThumbnails_hpp_ + +#include "../Point.hpp" +#include "../PrintConfig.hpp" +#include "ThumbnailData.hpp" + +#include +#include +#include + +namespace Slic3r::GCodeThumbnails { + +struct CompressedImageBuffer +{ + void *data { nullptr }; + size_t size { 0 }; + virtual ~CompressedImageBuffer() {} + virtual std::string_view tag() const = 0; +}; + +std::unique_ptr compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format); + +template +inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) +{ + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) { + static constexpr const size_t max_row_length = 78; + ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true }); + for (const ThumbnailData& data : thumbnails) + if (data.is_valid()) { + auto compressed = compress_thumbnail(data, format); + if (compressed->data && compressed->size) { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size)); + encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data, compressed->size)); + + output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size()).str().c_str()); + + while (encoded.size() > max_row_length) { + output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); + encoded = encoded.substr(max_row_length); + } + + if (encoded.size() > 0) + output((boost::format("; %s\n") % encoded).str().c_str()); + + output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str()); + } + throw_if_canceled(); + } + } +} + +} // namespace Slic3r::GCodeThumbnails + +#endif // slic3r_GCodeThumbnails_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index e65b604df1..f8361f1fd1 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -484,7 +484,7 @@ static std::vector s_Preset_printer_options { "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", "remaining_times", "silent_mode", - "machine_limits_usage", "thumbnails" + "machine_limits_usage", "thumbnails", "thumbnails_format" }; static std::vector s_Preset_sla_print_options { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index dc47b382da..9f848b49b6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -130,7 +130,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "start_gcode", "start_filament_gcode", "toolchange_gcode", - "threads", + "thumbnails", + "thumbnails_format", "use_firmware_retraction", "use_relative_e_distances", "use_volumetric_e", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1aaf8c1dde..173aa4cee0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -43,7 +43,7 @@ static t_config_enum_values s_keys_map_PrinterTechnology { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology) -static t_config_enum_values s_keys_map_GCodeFlavor { +static const t_config_enum_values s_keys_map_GCodeFlavor { { "reprap", gcfRepRapSprinter }, { "reprapfirmware", gcfRepRapFirmware }, { "repetier", gcfRepetier }, @@ -59,14 +59,14 @@ static t_config_enum_values s_keys_map_GCodeFlavor { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeFlavor) -static t_config_enum_values s_keys_map_MachineLimitsUsage { +static const t_config_enum_values s_keys_map_MachineLimitsUsage { { "emit_to_gcode", int(MachineLimitsUsage::EmitToGCode) }, { "time_estimate_only", int(MachineLimitsUsage::TimeEstimateOnly) }, { "ignore", int(MachineLimitsUsage::Ignore) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) -static t_config_enum_values s_keys_map_PrintHostType { +static const t_config_enum_values s_keys_map_PrintHostType { { "prusalink", htPrusaLink }, { "octoprint", htOctoPrint }, { "duet", htDuet }, @@ -77,20 +77,20 @@ static t_config_enum_values s_keys_map_PrintHostType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) -static t_config_enum_values s_keys_map_AuthorizationType { +static const t_config_enum_values s_keys_map_AuthorizationType { { "key", atKeyPassword }, { "user", atUserPassword } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(AuthorizationType) -static t_config_enum_values s_keys_map_FuzzySkinType { +static const t_config_enum_values s_keys_map_FuzzySkinType { { "none", int(FuzzySkinType::None) }, { "external", int(FuzzySkinType::External) }, { "all", int(FuzzySkinType::All) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FuzzySkinType) -static t_config_enum_values s_keys_map_InfillPattern { +static const t_config_enum_values s_keys_map_InfillPattern { { "rectilinear", ipRectilinear }, { "monotonic", ipMonotonic }, { "alignedrectilinear", ipAlignedRectilinear }, @@ -114,41 +114,41 @@ static t_config_enum_values s_keys_map_InfillPattern { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) -static t_config_enum_values s_keys_map_IroningType { +static const t_config_enum_values s_keys_map_IroningType { { "top", int(IroningType::TopSurfaces) }, { "topmost", int(IroningType::TopmostOnly) }, { "solid", int(IroningType::AllSolid) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(IroningType) -static t_config_enum_values s_keys_map_SlicingMode { +static const t_config_enum_values s_keys_map_SlicingMode { { "regular", int(SlicingMode::Regular) }, { "even_odd", int(SlicingMode::EvenOdd) }, { "close_holes", int(SlicingMode::CloseHoles) } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode) -static t_config_enum_values s_keys_map_SupportMaterialPattern { +static const t_config_enum_values s_keys_map_SupportMaterialPattern { { "rectilinear", smpRectilinear }, { "rectilinear-grid", smpRectilinearGrid }, { "honeycomb", smpHoneycomb } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern) -static t_config_enum_values s_keys_map_SupportMaterialStyle { +static const t_config_enum_values s_keys_map_SupportMaterialStyle { { "grid", smsGrid }, { "snug", smsSnug } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialStyle) -static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { +static const t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { { "auto", smipAuto }, { "rectilinear", smipRectilinear }, { "concentric", smipConcentric } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialInterfacePattern) -static t_config_enum_values s_keys_map_SeamPosition { +static const t_config_enum_values s_keys_map_SeamPosition { { "random", spRandom }, { "nearest", spNearest }, { "aligned", spAligned }, @@ -190,6 +190,13 @@ static const t_config_enum_values s_keys_map_DraftShield = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield) +static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = { + { "PNG", int(GCodeThumbnailsFormat::PNG) }, + { "JPG", int(GCodeThumbnailsFormat::JPG) }, + { "QOI", int(GCodeThumbnailsFormat::QOI) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat) + static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = { { "disable", ForwardCompatibilitySubstitutionRule::Disable }, { "enable", ForwardCompatibilitySubstitutionRule::Enable }, @@ -259,6 +266,16 @@ void PrintConfigDef::init_common_params() def->gui_type = ConfigOptionDef::GUIType::one_string; def->set_default_value(new ConfigOptionPoints()); + def = this->add("thumbnails_format", coEnum); + def->label = L("Format of G-code thumbnails"); + def->tooltip = L("Format of G-code thumbnails: PNG for best quality, JPG for smallest size, QOI for low memory firmware"); + def->mode = comExpert; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("PNG"); + def->enum_values.push_back("JPG"); + def->enum_values.push_back("QOI"); + def->set_default_value(new ConfigOptionEnum(GCodeThumbnailsFormat::PNG)); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2cc758e7be..077969d615 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -131,6 +131,10 @@ enum DraftShield { dsDisabled, dsLimited, dsEnabled }; +enum class GCodeThumbnailsFormat { + PNG, JPG, QOI +}; + #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); @@ -152,6 +156,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS diff --git a/src/qoi/CMakeLists.txt b/src/qoi/CMakeLists.txt new file mode 100644 index 0000000000..af8bf2e051 --- /dev/null +++ b/src/qoi/CMakeLists.txt @@ -0,0 +1,9 @@ +# PrusaSlicer specific CMake + +cmake_minimum_required(VERSION 2.8.12) +project(qoi) + +add_library(qoi STATIC + qoi.h + qoilib.c +) diff --git a/src/qoi/README.md b/src/qoi/README.md new file mode 100644 index 0000000000..4006c38507 --- /dev/null +++ b/src/qoi/README.md @@ -0,0 +1,108 @@ +Bundled with PrusaSlicer: commit 6c0831f91ffde5dfe2ceef32cbaff91d62b0e0ee +Original README follows: + + +![QOI Logo](https://qoiformat.org/qoi-logo.svg) + +# QOI - The “Quite OK Image Format” for fast, lossless image compression + +Single-file MIT licensed library for C/C++ + +See [qoi.h](https://github.com/phoboslab/qoi/blob/master/qoi.h) for +the documentation and format specification. + +More info at https://qoiformat.org + + +## Why? + +Compared to stb_image and stb_image_write QOI offers 20x-50x faster encoding, +3x-4x faster decoding and 20% better compression. It's also stupidly simple and +fits in about 300 lines of C. + + +## Example Usage + +- [qoiconv.c](https://github.com/phoboslab/qoi/blob/master/qoiconv.c) +converts between png <> qoi + - [qoibench.c](https://github.com/phoboslab/qoi/blob/master/qoibench.c) +a simple wrapper to benchmark stbi, libpng and qoi + + +## Limitations + +The QOI file format allows for huge images with up to 18 exa-pixels. A streaming +en-/decoder can handle these with minimal RAM requirements, assuming there is +enough storage space. + +This particular implementation of QOI however is limited to images with a +maximum size of 400 million pixels. It will safely refuse to en-/decode anything +larger than that. This is not a streaming en-/decoder. It loads the whole image +file into RAM before doing any work and is not extensively optimized for +performance (but it's still very fast). + +If this is a limitation for your use case, please look into any of the other +implementations listed below. + + +## Tools + +- https://github.com/floooh/qoiview - native QOI viewer +- https://github.com/pfusik/qoi-ci/releases/tag/qoi-ci-1.1.0 - QOI Plugin installer for GIMP, Imagine, Paint.NET and XnView MP +- https://github.com/iOrange/QoiFileTypeNet/releases/tag/v0.2 - QOI Plugin for Paint.NET +- https://github.com/iOrange/QOIThumbnailProvider - Add thumbnails for QOI images in Windows Explorer +- https://github.com/Tom94/tev - another native QOI viewer (allows pixel peeping and comparison with other image formats) +- https://apps.apple.com/br/app/qoiconverterx/id1602159820 QOI <=> PNG converter available on the Mac App Store +- https://github.com/kaetemi/qoi-max - QOI Bitmap I/O Plugin for 3ds Max +- https://raylibtech.itch.io/rtexviewer - texture viewer, supports QOI + + +## Implementations & Bindings of QOI + +- https://github.com/pfusik/qoi-ci (Ć, transpiled to C, C++, C#, Java, JavaScript, Python and Swift) +- https://github.com/kodonnell/qoi (Python) +- https://github.com/Cr4xy/lua-qoi (Lua) +- https://github.com/superzazu/SDL_QOI (C, SDL2 bindings) +- https://github.com/saharNooby/qoi-java (Java) +- https://github.com/MasterQ32/zig-qoi (Zig) +- https://github.com/rbino/qoix (Elixir) +- https://github.com/NUlliiON/QoiSharp (C#) +- https://github.com/aldanor/qoi-rust (Rust) +- https://github.com/zakarumych/rapid-qoi (Rust) +- https://github.com/takeyourhatoff/qoi (Go) +- https://github.com/DosWorld/pasqoi (Pascal) +- https://github.com/elihwyma/Swift-QOI (Swift) +- https://github.com/xfmoulet/qoi (Go) +- https://erratique.ch/software/qoic (OCaml) +- https://github.com/arian/go-qoi (Go) +- https://github.com/kchapelier/qoijs (JavaScript) +- https://github.com/KristofferC/QOI.jl (Julia) +- https://github.com/shadowMitia/libqoi/ (C++) +- https://github.com/MKCG/php-qoi (PHP) +- https://github.com/LightHouseSoftware/qoiformats (D) +- https://github.com/mhoward540/qoi-nim (Nim) + + +## QOI Support in Other Software + +- [SerenityOS](https://github.com/SerenityOS/serenity) supports decoding QOI system wide through a custom [cpp implementation in LibGfx](https://github.com/SerenityOS/serenity/blob/master/Userland/Libraries/LibGfx/QOILoader.h) +- [Raylib](https://github.com/raysan5/raylib) supports decoding and encoding QOI textures through its [rtextures module](https://github.com/raysan5/raylib/blob/master/src/rtextures.c) +- [Rebol3](https://github.com/Oldes/Rebol3/issues/39) supports decoding and encoding QOI using a native codec +- [c-ray](https://github.com/vkoskiv/c-ray) supports QOI natively +- [SAIL](https://github.com/HappySeaFox/sail) image decoding library, supports decoding and encoding QOI images +- [Orx](https://github.com/orx/orx) 2D game engine, supports QOI natively + + +## Packages + +[AUR](https://aur.archlinux.org/pkgbase/qoi-git/) - system-wide qoi.h, qoiconv and qoibench install as split packages. + + +## Implementations not yet conforming to the final specification + +These implementations are based on the pre-release version of QOI. Resulting files are not compatible with the current version. + +- https://github.com/ChevyRay/qoi_rs (Rust) +- https://github.com/panzi/jsqoi (TypeScript) +- https://github.com/0xd34df00d/hsqoi (Haskell) + diff --git a/src/qoi/qoi.h b/src/qoi/qoi.h new file mode 100644 index 0000000000..988f9edcb4 --- /dev/null +++ b/src/qoi/qoi.h @@ -0,0 +1,671 @@ +/* + +QOI - The "Quite OK Image" format for fast, lossless image compression + +Dominic Szablewski - https://phoboslab.org + + +-- LICENSE: The MIT License(MIT) + +Copyright(c) 2021 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +-- About + +QOI encodes and decodes images in a lossless format. Compared to stb_image and +stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and +20% better compression. + + +-- Synopsis + +// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +// Encode and store an RGBA buffer to the file system. The qoi_desc describes +// the input pixel data. +qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){ + .width = 1920, + .height = 1080, + .channels = 4, + .colorspace = QOI_SRGB +}); + +// Load and decode a QOI image from the file system into a 32bbp RGBA buffer. +// The qoi_desc struct will be filled with the width, height, number of channels +// and colorspace read from the file header. +qoi_desc desc; +void *rgba_pixels = qoi_read("image.qoi", &desc, 4); + + + +-- Documentation + +This library provides the following functions; +- qoi_read -- read and decode a QOI file +- qoi_decode -- decode the raw bytes of a QOI image from memory +- qoi_write -- encode and write a QOI file +- qoi_encode -- encode an rgba buffer into a QOI image in memory + +See the function declaration below for the signature and more information. + +If you don't want/need the qoi_read and qoi_write functions, you can define +QOI_NO_STDIO before including this library. + +This library uses malloc() and free(). To supply your own malloc implementation +you can define QOI_MALLOC and QOI_FREE before including this library. + +This library uses memset() to zero-initialize the index. To supply your own +implementation you can define QOI_ZEROARR before including this library. + + +-- Data Format + +A QOI file has a 14 byte header, followed by any number of data "chunks" and an +8-byte end marker. + +struct qoi_header_t { + char magic[4]; // magic bytes "qoif" + uint32_t width; // image width in pixels (BE) + uint32_t height; // image height in pixels (BE) + uint8_t channels; // 3 = RGB, 4 = RGBA + uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear +}; + +Images are encoded row by row, left to right, top to bottom. The decoder and +encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An +image is complete when all pixels specified by width * height have been covered. + +Pixels are encoded as + - a run of the previous pixel + - an index into an array of previously seen pixels + - a difference to the previous pixel value in r,g,b + - full r,g,b or r,g,b,a values + +The color channels are assumed to not be premultiplied with the alpha channel +("un-premultiplied alpha"). + +A running array[64] (zero-initialized) of previously seen pixel values is +maintained by the encoder and decoder. Each pixel that is seen by the encoder +and decoder is put into this array at the position formed by a hash function of +the color value. In the encoder, if the pixel value at the index matches the +current pixel, this index position is written to the stream as QOI_OP_INDEX. +The hash function for the index is: + + index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64 + +Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The +bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All +values encoded in these data bits have the most significant bit on the left. + +The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the +presence of an 8-bit tag first. + +The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte. + + +The possible chunks are: + + +.- QOI_OP_INDEX ----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 0 0 | index | +`-------------------------` +2-bit tag b00 +6-bit index into the color index array: 0..63 + +A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the +same index. QOI_OP_RUN should be used instead. + + +.- QOI_OP_DIFF -----------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----+-----+-----| +| 0 1 | dr | dg | db | +`-------------------------` +2-bit tag b01 +2-bit red channel difference from the previous pixel between -2..1 +2-bit green channel difference from the previous pixel between -2..1 +2-bit blue channel difference from the previous pixel between -2..1 + +The difference to the current channel values are using a wraparound operation, +so "1 - 2" will result in 255, while "255 + 1" will result in 0. + +Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as +0 (b00). 1 is stored as 3 (b11). + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_LUMA -------------------------------------. +| Byte[0] | Byte[1] | +| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | +|-------+-----------------+-------------+-----------| +| 1 0 | green diff | dr - dg | db - dg | +`---------------------------------------------------` +2-bit tag b10 +6-bit green channel difference from the previous pixel -32..31 +4-bit red channel difference minus green channel difference -8..7 +4-bit blue channel difference minus green channel difference -8..7 + +The green channel is used to indicate the general direction of change and is +encoded in 6 bits. The red and blue channels (dr and db) base their diffs off +of the green channel difference and are encoded in 4 bits. I.e.: + dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g) + db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g) + +The difference to the current channel values are using a wraparound operation, +so "10 - 13" will result in 253, while "250 + 7" will result in 1. + +Values are stored as unsigned integers with a bias of 32 for the green channel +and a bias of 8 for the red and blue channel. + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RUN ------------. +| Byte[0] | +| 7 6 5 4 3 2 1 0 | +|-------+-----------------| +| 1 1 | run | +`-------------------------` +2-bit tag b11 +6-bit run-length repeating the previous pixel: 1..62 + +The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64 +(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and +QOI_OP_RGBA tags. + + +.- QOI_OP_RGB ------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------| +| 1 1 1 1 1 1 1 0 | red | green | blue | +`-------------------------------------------------------` +8-bit tag b11111110 +8-bit red channel value +8-bit green channel value +8-bit blue channel value + +The alpha value remains unchanged from the previous pixel. + + +.- QOI_OP_RGBA ---------------------------------------------------. +| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] | +| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | +|-------------------------+---------+---------+---------+---------| +| 1 1 1 1 1 1 1 1 | red | green | blue | alpha | +`-----------------------------------------------------------------` +8-bit tag b11111111 +8-bit red channel value +8-bit green channel value +8-bit blue channel value +8-bit alpha channel value + +*/ + + +/* ----------------------------------------------------------------------------- +Header - Public functions */ + +#ifndef QOI_H +#define QOI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions. +It describes either the input format (for qoi_write and qoi_encode), or is +filled with the description read from the file header (for qoi_read and +qoi_decode). + +The colorspace in this qoi_desc is an enum where + 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel + 1 = all channels are linear +You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely +informative. It will be saved to the file header, but does not affect +how chunks are en-/decoded. */ + +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_NO_STDIO + +/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file +system. The qoi_desc struct must be filled with the image width, height, +number of channels (3 = RGB, 4 = RGBA) and the colorspace. + +The function returns 0 on failure (invalid parameters, or fopen or malloc +failed) or the number of bytes written on success. */ + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc); + + +/* Read and decode a QOI image from the file system. If channels is 0, the +number of channels from the file header is used. If channels is 3 or 4 the +output format will be forced into this number of channels. + +The function either returns NULL on failure (invalid data, or malloc or fopen +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +will be filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_read(const char *filename, qoi_desc *desc, int channels); + +#endif /* QOI_NO_STDIO */ + + +/* Encode raw RGB or RGBA pixels into a QOI image in memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the encoded data on success. On success the out_len +is set to the size in bytes of the encoded data. + +The returned qoi data should be free()d after use. */ + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len); + + +/* Decode a QOI image from memory. + +The function either returns NULL on failure (invalid parameters or malloc +failed) or a pointer to the decoded pixels. On success, the qoi_desc struct +is filled with the description from the file header. + +The returned pixel data should be free()d after use. */ + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels); + + +#ifdef __cplusplus +} +#endif +#endif /* QOI_H */ + + +/* ----------------------------------------------------------------------------- +Implementation */ + +#ifdef QOI_IMPLEMENTATION +#include +#include + +#ifndef QOI_MALLOC + #define QOI_MALLOC(sz) malloc(sz) + #define QOI_FREE(p) free(p) +#endif +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; + +static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { + bytes[(*p)++] = (0xff000000 & v) >> 24; + bytes[(*p)++] = (0x00ff0000 & v) >> 16; + bytes[(*p)++] = (0x0000ff00 & v) >> 8; + bytes[(*p)++] = (0x000000ff & v); +} + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + +void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { + int i, max_size, p, run; + int px_len, px_end, px_pos, channels; + unsigned char *bytes; + const unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px, px_prev; + + if ( + data == NULL || out_len == NULL || desc == NULL || + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + max_size = + desc->width * desc->height * (desc->channels + 1) + + QOI_HEADER_SIZE + sizeof(qoi_padding); + + p = 0; + bytes = (unsigned char *) QOI_MALLOC(max_size); + if (!bytes) { + return NULL; + } + + qoi_write_32(bytes, &p, QOI_MAGIC); + qoi_write_32(bytes, &p, desc->width); + qoi_write_32(bytes, &p, desc->height); + bytes[p++] = desc->channels; + bytes[p++] = desc->colorspace; + + + pixels = (const unsigned char *)data; + + QOI_ZEROARR(index); + + run = 0; + px_prev.rgba.r = 0; + px_prev.rgba.g = 0; + px_prev.rgba.b = 0; + px_prev.rgba.a = 255; + px = px_prev; + + px_len = desc->width * desc->height * desc->channels; + px_end = px_len - desc->channels; + channels = desc->channels; + + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (channels == 4) { + px = *(qoi_rgba_t *)(pixels + px_pos); + } + else { + px.rgba.r = pixels[px_pos + 0]; + px.rgba.g = pixels[px_pos + 1]; + px.rgba.b = pixels[px_pos + 2]; + } + + if (px.v == px_prev.v) { + run++; + if (run == 62 || px_pos == px_end) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + } + else { + int index_pos; + + if (run > 0) { + bytes[p++] = QOI_OP_RUN | (run - 1); + run = 0; + } + + index_pos = QOI_COLOR_HASH(px) % 64; + + if (index[index_pos].v == px.v) { + bytes[p++] = QOI_OP_INDEX | index_pos; + } + else { + index[index_pos] = px; + + if (px.rgba.a == px_prev.rgba.a) { + signed char vr = px.rgba.r - px_prev.rgba.r; + signed char vg = px.rgba.g - px_prev.rgba.g; + signed char vb = px.rgba.b - px_prev.rgba.b; + + signed char vg_r = vr - vg; + signed char vg_b = vb - vg; + + if ( + vr > -3 && vr < 2 && + vg > -3 && vg < 2 && + vb > -3 && vb < 2 + ) { + bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); + } + else if ( + vg_r > -9 && vg_r < 8 && + vg > -33 && vg < 32 && + vg_b > -9 && vg_b < 8 + ) { + bytes[p++] = QOI_OP_LUMA | (vg + 32); + bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); + } + else { + bytes[p++] = QOI_OP_RGB; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + } + } + else { + bytes[p++] = QOI_OP_RGBA; + bytes[p++] = px.rgba.r; + bytes[p++] = px.rgba.g; + bytes[p++] = px.rgba.b; + bytes[p++] = px.rgba.a; + } + } + } + px_prev = px; + } + + for (i = 0; i < (int)sizeof(qoi_padding); i++) { + bytes[p++] = qoi_padding[i]; + } + + *out_len = p; + return bytes; +} + +void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { + const unsigned char *bytes; + unsigned int header_magic; + unsigned char *pixels; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + if ( + data == NULL || desc == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + return NULL; + } + + bytes = (const unsigned char *)data; + + header_magic = qoi_read_32(bytes, &p); + desc->width = qoi_read_32(bytes, &p); + desc->height = qoi_read_32(bytes, &p); + desc->channels = bytes[p++]; + desc->colorspace = bytes[p++]; + + if ( + desc->width == 0 || desc->height == 0 || + desc->channels < 3 || desc->channels > 4 || + desc->colorspace > 1 || + header_magic != QOI_MAGIC || + desc->height >= QOI_PIXELS_MAX / desc->width + ) { + return NULL; + } + + if (channels == 0) { + channels = desc->channels; + } + + px_len = desc->width * desc->height * channels; + pixels = (unsigned char *) QOI_MALLOC(px_len); + if (!pixels) { + return NULL; + } + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; + } + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; + } + + if (channels == 4) { + *(qoi_rgba_t*)(pixels + px_pos) = px; + } + else { + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + } + } + + return pixels; +} + +#ifndef QOI_NO_STDIO +#include + +int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { + FILE *f = fopen(filename, "wb"); + int size; + void *encoded; + + if (!f) { + return 0; + } + + encoded = qoi_encode(data, desc, &size); + if (!encoded) { + fclose(f); + return 0; + } + + fwrite(encoded, 1, size, f); + fclose(f); + + QOI_FREE(encoded); + return size; +} + +void *qoi_read(const char *filename, qoi_desc *desc, int channels) { + FILE *f = fopen(filename, "rb"); + int size, bytes_read; + void *pixels, *data; + + if (!f) { + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + if (size <= 0) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + data = QOI_MALLOC(size); + if (!data) { + fclose(f); + return NULL; + } + + bytes_read = fread(data, 1, size, f); + fclose(f); + + pixels = qoi_decode(data, bytes_read, desc, channels); + QOI_FREE(data); + return pixels; +} + +#endif /* QOI_NO_STDIO */ +#endif /* QOI_IMPLEMENTATION */ diff --git a/src/qoi/qoilib.c b/src/qoi/qoilib.c new file mode 100644 index 0000000000..e3aa809c74 --- /dev/null +++ b/src/qoi/qoilib.c @@ -0,0 +1,6 @@ +// PrusaSlicer specific: +// Include and compile QOI library. + +#define QOI_IMPLEMENTATION +#define QOI_NO_STDIO +#include "qoi.h" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4f19ad8067..7d9277dd95 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2324,6 +2324,7 @@ void TabPrinter::build_fff() option = optgroup->get_option("thumbnails"); option.opt.full_width = true; optgroup->append_single_option_line(option); + optgroup->append_single_option_line("thumbnails_format"); optgroup->append_single_option_line("silent_mode"); optgroup->append_single_option_line("remaining_times"); From 086662b6a1c7d7a14e35a90e6d46445f6871ac24 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 2 Feb 2022 17:43:39 +0100 Subject: [PATCH 06/22] Fixed typo in thumbnail_QOI tag. --- src/libslic3r/GCode/Thumbnails.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp index d04d3edcd7..655997be1a 100644 --- a/src/libslic3r/GCode/Thumbnails.cpp +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -16,7 +16,7 @@ struct CompressedPNG : CompressedImageBuffer struct CompressedQOI : CompressedImageBuffer { ~CompressedQOI() override { free(data); } - std::string_view tag() const override { return "thumbnail_OQI"sv; } + std::string_view tag() const override { return "thumbnail_QOI"sv; } }; std::unique_ptr compress_thumbnail_png(const ThumbnailData &data) From bd65eb55b09fdf95a9a5ae131ba63b994226dc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Feb 2022 08:36:24 +0100 Subject: [PATCH 07/22] Added a missing include (GCC11.1 without PCH). --- src/libslic3r/GCode/Thumbnails.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp index fe7ab69c51..30bb6b653b 100644 --- a/src/libslic3r/GCode/Thumbnails.hpp +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -9,6 +9,8 @@ #include #include +#include + namespace Slic3r::GCodeThumbnails { struct CompressedImageBuffer From 3d0feaf3e66b3245886f9db597d47399dcb1c9c8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 3 Feb 2022 11:02:10 +0100 Subject: [PATCH 08/22] Fix for #7856 - Grey square rendered over "notebook tabs" in preferences dialog For BlinkingBitmap was used wrong parent. That is why first layout wasn't correct. --- src/slic3r/GUI/Preferences.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 2af008a52b..75c5c116fd 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -790,10 +790,10 @@ void PreferencesDialog::create_settings_mode_widget() } std::string opt_key = "settings_layout_mode"; - m_blinkers[opt_key] = new BlinkingBitmap(this); + m_blinkers[opt_key] = new BlinkingBitmap(parent); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); @@ -810,13 +810,13 @@ void PreferencesDialog::create_settings_text_color_widget() if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); std::string opt_key = "text_colors"; - m_blinkers[opt_key] = new BlinkingBitmap(this); + m_blinkers[opt_key] = new BlinkingBitmap(parent); wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); From 030f4601149704fb3213ef44ca9c9910a1548ed0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 3 Feb 2022 13:24:30 +0100 Subject: [PATCH 09/22] Follow-up of 87cff55856ae14096cd57f8e2542f1f1caef7167 - Implementation of compress_thumbnail_jpg() --- src/CMakeLists.txt | 1 + src/jpeg-compressor/CMakeLists.txt | 9 + src/jpeg-compressor/README.md | 15 + src/jpeg-compressor/jpge.cpp | 1076 ++++++++++++++++++++++++++++ src/jpeg-compressor/jpge.h | 173 +++++ src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/GCode/Thumbnails.cpp | 32 +- 7 files changed, 1304 insertions(+), 3 deletions(-) create mode 100644 src/jpeg-compressor/CMakeLists.txt create mode 100644 src/jpeg-compressor/README.md create mode 100644 src/jpeg-compressor/jpge.cpp create mode 100644 src/jpeg-compressor/jpge.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab1c7b9645..e5ee9b3ee8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) add_subdirectory(qoi) +add_subdirectory(jpeg-compressor) # Adding libnest2d project for bin packing... add_subdirectory(libnest2d) diff --git a/src/jpeg-compressor/CMakeLists.txt b/src/jpeg-compressor/CMakeLists.txt new file mode 100644 index 0000000000..7d404dc995 --- /dev/null +++ b/src/jpeg-compressor/CMakeLists.txt @@ -0,0 +1,9 @@ +# PrusaSlicer specific CMake + +cmake_minimum_required(VERSION 2.8.12) +project(jpeg-compressor) + +add_library(jpeg-compressor STATIC + jpge.h + jpge.cpp +) diff --git a/src/jpeg-compressor/README.md b/src/jpeg-compressor/README.md new file mode 100644 index 0000000000..2722225e67 --- /dev/null +++ b/src/jpeg-compressor/README.md @@ -0,0 +1,15 @@ +** jpeg-compressor is a C++ JPEG compression/fuzzed low-RAM JPEG decompression codec.** + +For more information go to https://github.com/richgel999/jpeg-compressor + +THIS DIRECTORY CONTAINS THE TWO FILES: + +jpge.h +jpge.cpp + +TAKEN FROM + +master branch + +ON 03 FEB 2022. + diff --git a/src/jpeg-compressor/jpge.cpp b/src/jpeg-compressor/jpge.cpp new file mode 100644 index 0000000000..4bdc34a97c --- /dev/null +++ b/src/jpeg-compressor/jpge.cpp @@ -0,0 +1,1076 @@ +// jpge.cpp - C++ class for JPEG compression. Richard Geldreich +// Supports grayscale, H1V1, H2V1, and H2V2 chroma subsampling factors, one or two pass Huffman table optimization, libjpeg-style quality 1-100 quality factors. +// Also supports using luma quantization tables for chroma. +// +// Released under two licenses. You are free to choose which license you want: +// License 1: +// Public Domain +// +// License 2: +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// v1.01, Dec. 18, 2010 - Initial release +// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.) +// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc. +// Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03). +// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug. +// Code tweaks to fix VS2008 static code analysis warnings (all looked harmless). +// Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02. +// v1.05, March 25, 2020: Added Apache 2.0 alternate license + +#include "jpge.h" + +#include +#include +#include + +#define JPGE_MAX(a,b) (((a)>(b))?(a):(b)) +#define JPGE_MIN(a,b) (((a)<(b))?(a):(b)) + +namespace jpge { + + static inline void* jpge_malloc(size_t nSize) { return malloc(nSize); } + static inline void jpge_free(void* p) { free(p); } + + // Various JPEG enums and tables. + enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 }; + enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 }; + + static uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; + static int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 }; + static int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; + + // Table from http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008 + // This is mozjpeg's default table, in zag order. + static int16 s_alt_quant[64] = { 16,16,16,16,17,16,18,20,20,18,25,27,24,27,25,37,34,31,31,34,37,56,40,43,40,43,40,56,85,53,62,53,53,62,53,85,75,91,74,69,74,91,75,135,106,94,94,106,135,156,131,124,131,156,189,169,169,189,238,226,238,311,311,418 }; + + static uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; + static uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; + static uint8 s_ac_lum_val[AC_LUM_CODES] = + { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + static uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; + static uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; + static uint8 s_ac_chroma_val[AC_CHROMA_CODES] = + { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + }; + + // Low-level helper functions. + template inline void clear_obj(T& obj) { memset(&obj, 0, sizeof(obj)); } + + const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329; + static inline uint8 clamp(int i) { if (static_cast(i) > 255U) { if (i < 0) i = 0; else if (i > 255) i = 255; } return static_cast(i); } + + static inline int left_shifti(int val, uint32 bits) + { + return static_cast(static_cast(val) << bits); + } + + static void RGB_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) + { + for (; num_pixels; pDst += 3, pSrc += 3, num_pixels--) + { + const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; + pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); + pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); + } + } + + static void RGB_to_Y(uint8* pDst, const uint8* pSrc, int num_pixels) + { + for (; num_pixels; pDst++, pSrc += 3, num_pixels--) + pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); + } + + static void RGBA_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) + { + for (; num_pixels; pDst += 3, pSrc += 4, num_pixels--) + { + const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; + pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); + pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); + pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); + } + } + + static void RGBA_to_Y(uint8* pDst, const uint8* pSrc, int num_pixels) + { + for (; num_pixels; pDst++, pSrc += 4, num_pixels--) + pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); + } + + static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) + { + for (; num_pixels; pDst += 3, pSrc++, num_pixels--) { pDst[0] = pSrc[0]; pDst[1] = 128; pDst[2] = 128; } + } + + // Forward DCT - DCT derived from jfdctint. + enum { CONST_BITS = 13, ROW_BITS = 2 }; +#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n)) +#define DCT_MUL(var, c) (static_cast(var) * static_cast(c)) +#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \ + int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \ + int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \ + int32 u1 = DCT_MUL(t12 + t13, 4433); \ + s2 = u1 + DCT_MUL(t13, 6270); \ + s6 = u1 + DCT_MUL(t12, -15137); \ + u1 = t4 + t7; \ + int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \ + int32 z5 = DCT_MUL(u3 + u4, 9633); \ + t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \ + t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \ + u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \ + u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \ + u3 += z5; u4 += z5; \ + s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3; + + static void DCT2D(int32* p) + { + int32 c, * q = p; + for (c = 7; c >= 0; c--, q += 8) + { + int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0] = left_shifti(s0, ROW_BITS); q[1] = DCT_DESCALE(s1, CONST_BITS - ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS - ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS - ROW_BITS); + q[4] = left_shifti(s4, ROW_BITS); q[5] = DCT_DESCALE(s5, CONST_BITS - ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS - ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS - ROW_BITS); + } + for (q = p, c = 7; c >= 0; c--, q++) + { + int32 s0 = q[0 * 8], s1 = q[1 * 8], s2 = q[2 * 8], s3 = q[3 * 8], s4 = q[4 * 8], s5 = q[5 * 8], s6 = q[6 * 8], s7 = q[7 * 8]; + DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); + q[0 * 8] = DCT_DESCALE(s0, ROW_BITS + 3); q[1 * 8] = DCT_DESCALE(s1, CONST_BITS + ROW_BITS + 3); q[2 * 8] = DCT_DESCALE(s2, CONST_BITS + ROW_BITS + 3); q[3 * 8] = DCT_DESCALE(s3, CONST_BITS + ROW_BITS + 3); + q[4 * 8] = DCT_DESCALE(s4, ROW_BITS + 3); q[5 * 8] = DCT_DESCALE(s5, CONST_BITS + ROW_BITS + 3); q[6 * 8] = DCT_DESCALE(s6, CONST_BITS + ROW_BITS + 3); q[7 * 8] = DCT_DESCALE(s7, CONST_BITS + ROW_BITS + 3); + } + } + + struct sym_freq { uint m_key, m_sym_index; }; + + // Radix sorts sym_freq[] array by 32-bit key m_key. Returns ptr to sorted values. + static inline sym_freq* radix_sort_syms(uint num_syms, sym_freq* pSyms0, sym_freq* pSyms1) + { + const uint cMaxPasses = 4; + uint32 hist[256 * cMaxPasses]; clear_obj(hist); + for (uint i = 0; i < num_syms; i++) { uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; hist[256 * 2 + ((freq >> 16) & 0xFF)]++; hist[256 * 3 + ((freq >> 24) & 0xFF)]++; } + sym_freq* pCur_syms = pSyms0, * pNew_syms = pSyms1; + uint total_passes = cMaxPasses; while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (uint pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const uint32* pHist = &hist[pass << 8]; + uint offsets[256], cur_ofs = 0; + for (uint i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (uint i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; + } + return pCur_syms; + } + + // calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. + static void calculate_minimum_redundancy(sym_freq* A, int n) + { + int root, leaf, next, avbl, used, dpth; + if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = next; } + else A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key += A[root].m_key; A[root++].m_key = next; } + else A[next].m_key += A[leaf++].m_key; + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } + while (avbl > used) { A[next--].m_key = dpth; avbl--; } + avbl = 2 * used; dpth++; used = 0; + } + } + + // Limits canonical Huffman code table's max code size to max_code_size. + static void huffman_enforce_max_code_size(int* pNum_codes, int code_list_len, int max_code_size) + { + if (code_list_len <= 1) return; + + for (int i = max_code_size + 1; i <= MAX_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + + uint32 total = 0; + for (int i = max_code_size; i > 0; i--) + total += (((uint32)pNum_codes[i]) << (max_code_size - i)); + + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (int i = max_code_size - 1; i > 0; i--) + { + if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + } + total--; + } + } + + // Generates an optimized offman table. + void jpeg_encoder::optimize_huffman_table(int table_num, int table_len) + { + sym_freq syms0[MAX_HUFF_SYMBOLS], syms1[MAX_HUFF_SYMBOLS]; + syms0[0].m_key = 1; syms0[0].m_sym_index = 0; // dummy symbol, assures that no valid code contains all 1's + int num_used_syms = 1; + const uint32* pSym_count = &m_huff_count[table_num][0]; + for (int i = 0; i < table_len; i++) + if (pSym_count[i]) { syms0[num_used_syms].m_key = pSym_count[i]; syms0[num_used_syms++].m_sym_index = i + 1; } + sym_freq* pSyms = radix_sort_syms(num_used_syms, syms0, syms1); + calculate_minimum_redundancy(pSyms, num_used_syms); + + // Count the # of symbols of each code size. + int num_codes[1 + MAX_HUFF_CODESIZE]; clear_obj(num_codes); + for (int i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + const uint JPGE_CODE_SIZE_LIMIT = 16; // the maximum possible size of a JPEG Huffman code (valid range is [9,16] - 9 vs. 8 because of the dummy symbol) + huffman_enforce_max_code_size(num_codes, num_used_syms, JPGE_CODE_SIZE_LIMIT); + + // Compute m_huff_bits array, which contains the # of symbols per code size. + clear_obj(m_huff_bits[table_num]); + for (int i = 1; i <= (int)JPGE_CODE_SIZE_LIMIT; i++) + m_huff_bits[table_num][i] = static_cast(num_codes[i]); + + // Remove the dummy symbol added above, which must be in largest bucket. + for (int i = JPGE_CODE_SIZE_LIMIT; i >= 1; i--) + { + if (m_huff_bits[table_num][i]) { m_huff_bits[table_num][i]--; break; } + } + + // Compute the m_huff_val array, which contains the symbol indices sorted by code size (smallest to largest). + for (int i = num_used_syms - 1; i >= 1; i--) + m_huff_val[table_num][num_used_syms - 1 - i] = static_cast(pSyms[i].m_sym_index - 1); + } + + // JPEG marker generation. + void jpeg_encoder::emit_byte(uint8 i) + { + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_obj(i); + } + + void jpeg_encoder::emit_word(uint i) + { + emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF)); + } + + void jpeg_encoder::emit_marker(int marker) + { + emit_byte(uint8(0xFF)); emit_byte(uint8(marker)); + } + + // Emit JFIF marker + void jpeg_encoder::emit_jfif_app0() + { + emit_marker(M_APP0); + emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); + emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */ + emit_byte(0); + emit_byte(1); /* Major version */ + emit_byte(1); /* Minor version */ + emit_byte(0); /* Density unit */ + emit_word(1); + emit_word(1); + emit_byte(0); /* No thumbnail image */ + emit_byte(0); + } + + // Emit quantization tables + void jpeg_encoder::emit_dqt() + { + for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++) + { + emit_marker(M_DQT); + emit_word(64 + 1 + 2); + emit_byte(static_cast(i)); + for (int j = 0; j < 64; j++) + emit_byte(static_cast(m_quantization_tables[i][j])); + } + } + + // Emit start of frame marker + void jpeg_encoder::emit_sof() + { + emit_marker(M_SOF0); /* baseline */ + emit_word(3 * m_num_components + 2 + 5 + 1); + emit_byte(8); /* precision */ + emit_word(m_image_y); + emit_word(m_image_x); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); /* component ID */ + emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */ + emit_byte(i > 0); /* quant. table num */ + } + } + + // Emit Huffman table. + void jpeg_encoder::emit_dht(uint8* bits, uint8* val, int index, bool ac_flag) + { + emit_marker(M_DHT); + + int length = 0; + for (int i = 1; i <= 16; i++) + length += bits[i]; + + emit_word(length + 2 + 1 + 16); + emit_byte(static_cast(index + (ac_flag << 4))); + + for (int i = 1; i <= 16; i++) + emit_byte(bits[i]); + + for (int i = 0; i < length; i++) + emit_byte(val[i]); + } + + // Emit all Huffman tables. + void jpeg_encoder::emit_dhts() + { + emit_dht(m_huff_bits[0 + 0], m_huff_val[0 + 0], 0, false); + emit_dht(m_huff_bits[2 + 0], m_huff_val[2 + 0], 0, true); + if (m_num_components == 3) + { + emit_dht(m_huff_bits[0 + 1], m_huff_val[0 + 1], 1, false); + emit_dht(m_huff_bits[2 + 1], m_huff_val[2 + 1], 1, true); + } + } + + // emit start of scan + void jpeg_encoder::emit_sos() + { + emit_marker(M_SOS); + emit_word(2 * m_num_components + 2 + 1 + 3); + emit_byte(m_num_components); + for (int i = 0; i < m_num_components; i++) + { + emit_byte(static_cast(i + 1)); + if (i == 0) + emit_byte((0 << 4) + 0); + else + emit_byte((1 << 4) + 1); + } + emit_byte(0); /* spectral selection */ + emit_byte(63); + emit_byte(0); + } + + // Emit all markers at beginning of image file. + void jpeg_encoder::emit_markers() + { + emit_marker(M_SOI); + emit_jfif_app0(); + emit_dqt(); + emit_sof(); + emit_dhts(); + emit_sos(); + } + + // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays. + void jpeg_encoder::compute_huffman_table(uint* codes, uint8* code_sizes, uint8* bits, uint8* val) + { + int i, l, last_p, si; + uint8 huff_size[257]; + uint huff_code[257]; + uint code; + + int p = 0; + for (l = 1; l <= 16; l++) + for (i = 1; i <= bits[l]; i++) + huff_size[p++] = (char)l; + + huff_size[p] = 0; last_p = p; // write sentinel + + code = 0; si = huff_size[0]; p = 0; + + while (huff_size[p]) + { + while (huff_size[p] == si) + huff_code[p++] = code++; + code <<= 1; + si++; + } + + memset(codes, 0, sizeof(codes[0]) * 256); + memset(code_sizes, 0, sizeof(code_sizes[0]) * 256); + for (p = 0; p < last_p; p++) + { + codes[val[p]] = huff_code[p]; + code_sizes[val[p]] = huff_size[p]; + } + } + + // Quantization table generation. + void jpeg_encoder::compute_quant_table(int32* pDst, int16* pSrc) + { + int32 q; + if (m_params.m_quality < 50) + q = 5000 / m_params.m_quality; + else + q = 200 - m_params.m_quality * 2; + for (int i = 0; i < 64; i++) + { + int32 j = *pSrc++; j = (j * q + 50L) / 100L; + *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255); + } + } + + // Higher-level methods. + void jpeg_encoder::first_pass_init() + { + m_bit_buffer = 0; m_bits_in = 0; + memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0])); + m_mcu_y_ofs = 0; + m_pass_num = 1; + } + + bool jpeg_encoder::second_pass_init() + { + compute_huffman_table(&m_huff_codes[0 + 0][0], &m_huff_code_sizes[0 + 0][0], m_huff_bits[0 + 0], m_huff_val[0 + 0]); + compute_huffman_table(&m_huff_codes[2 + 0][0], &m_huff_code_sizes[2 + 0][0], m_huff_bits[2 + 0], m_huff_val[2 + 0]); + if (m_num_components > 1) + { + compute_huffman_table(&m_huff_codes[0 + 1][0], &m_huff_code_sizes[0 + 1][0], m_huff_bits[0 + 1], m_huff_val[0 + 1]); + compute_huffman_table(&m_huff_codes[2 + 1][0], &m_huff_code_sizes[2 + 1][0], m_huff_bits[2 + 1], m_huff_val[2 + 1]); + } + first_pass_init(); + emit_markers(); + m_pass_num = 2; + return true; + } + + bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels) + { + m_num_components = 3; + switch (m_params.m_subsampling) + { + case Y_ONLY: + { + m_num_components = 1; + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H1V1: + { + m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 8; m_mcu_y = 8; + break; + } + case H2V1: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 8; + break; + } + case H2V2: + { + m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2; + m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; + m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; + m_mcu_x = 16; m_mcu_y = 16; + } + } + + m_image_x = p_x_res; m_image_y = p_y_res; + m_image_bpp = src_channels; + m_image_bpl = m_image_x * src_channels; + m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1)); + m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1)); + m_image_bpl_xlt = m_image_x * m_num_components; + m_image_bpl_mcu = m_image_x_mcu * m_num_components; + m_mcus_per_row = m_image_x_mcu / m_mcu_x; + + if ((m_mcu_lines[0] = static_cast(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) return false; + for (int i = 1; i < m_mcu_y; i++) + m_mcu_lines[i] = m_mcu_lines[i - 1] + m_image_bpl_mcu; + + if (m_params.m_use_std_tables) + { + compute_quant_table(m_quantization_tables[0], s_std_lum_quant); + compute_quant_table(m_quantization_tables[1], m_params.m_no_chroma_discrim_flag ? s_std_lum_quant : s_std_croma_quant); + } + else + { + compute_quant_table(m_quantization_tables[0], s_alt_quant); + memcpy(m_quantization_tables[1], m_quantization_tables[0], sizeof(m_quantization_tables[1])); + } + + m_out_buf_left = JPGE_OUT_BUF_SIZE; + m_pOut_buf = m_out_buf; + + if (m_params.m_two_pass_flag) + { + clear_obj(m_huff_count); + first_pass_init(); + } + else + { + memcpy(m_huff_bits[0 + 0], s_dc_lum_bits, 17); memcpy(m_huff_val[0 + 0], s_dc_lum_val, DC_LUM_CODES); + memcpy(m_huff_bits[2 + 0], s_ac_lum_bits, 17); memcpy(m_huff_val[2 + 0], s_ac_lum_val, AC_LUM_CODES); + memcpy(m_huff_bits[0 + 1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0 + 1], s_dc_chroma_val, DC_CHROMA_CODES); + memcpy(m_huff_bits[2 + 1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2 + 1], s_ac_chroma_val, AC_CHROMA_CODES); + if (!second_pass_init()) return false; // in effect, skip over the first pass + } + return m_all_stream_writes_succeeded; + } + + void jpeg_encoder::load_block_8_8_grey(int x) + { + uint8* pSrc; + sample_array_t* pDst = m_sample_array; + x <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[i] + x; + pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128; + pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128; + } + } + + void jpeg_encoder::load_block_8_8(int x, int y, int c) + { + uint8* pSrc; + sample_array_t* pDst = m_sample_array; + x = (x * (8 * 3)) + c; + y <<= 3; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc = m_mcu_lines[y + i] + x; + pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128; + pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128; + } + } + + void jpeg_encoder::load_block_16_8(int x, int c) + { + uint8* pSrc1, * pSrc2; + sample_array_t* pDst = m_sample_array; + x = (x * (16 * 3)) + c; + for (int i = 0; i < 16; i += 2, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pSrc2 = m_mcu_lines[i + 1] + x; + pDst[0] = ((pSrc1[0 * 3] + pSrc1[1 * 3] + pSrc2[0 * 3] + pSrc2[1 * 3] + 2) >> 2) - 128; pDst[1] = ((pSrc1[2 * 3] + pSrc1[3 * 3] + pSrc2[2 * 3] + pSrc2[3 * 3] + 2) >> 2) - 128; + pDst[2] = ((pSrc1[4 * 3] + pSrc1[5 * 3] + pSrc2[4 * 3] + pSrc2[5 * 3] + 2) >> 2) - 128; pDst[3] = ((pSrc1[6 * 3] + pSrc1[7 * 3] + pSrc2[6 * 3] + pSrc2[7 * 3] + 2) >> 2) - 128; + pDst[4] = ((pSrc1[8 * 3] + pSrc1[9 * 3] + pSrc2[8 * 3] + pSrc2[9 * 3] + 2) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + 2) >> 2) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + 2) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + 2) >> 2) - 128; + } + } + + void jpeg_encoder::load_block_16_8_8(int x, int c) + { + uint8* pSrc1; + sample_array_t* pDst = m_sample_array; + x = (x * (16 * 3)) + c; + for (int i = 0; i < 8; i++, pDst += 8) + { + pSrc1 = m_mcu_lines[i + 0] + x; + pDst[0] = ((pSrc1[0 * 3] + pSrc1[1 * 3] + 1) >> 1) - 128; pDst[1] = ((pSrc1[2 * 3] + pSrc1[3 * 3] + 1) >> 1) - 128; + pDst[2] = ((pSrc1[4 * 3] + pSrc1[5 * 3] + 1) >> 1) - 128; pDst[3] = ((pSrc1[6 * 3] + pSrc1[7 * 3] + 1) >> 1) - 128; + pDst[4] = ((pSrc1[8 * 3] + pSrc1[9 * 3] + 1) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + 1) >> 1) - 128; + pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + 1) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + 1) >> 1) - 128; + } + } + + void jpeg_encoder::load_quantized_coefficients(int component_num) + { + int32* q = m_quantization_tables[component_num > 0]; + int16* pDst = m_coefficient_array; + for (int i = 0; i < 64; i++) + { + sample_array_t j = m_sample_array[s_zag[i]]; + if (j < 0) + { + if ((j = -j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast(-(j / *q)); + } + else + { + if ((j = j + (*q >> 1)) < *q) + *pDst++ = 0; + else + *pDst++ = static_cast((j / *q)); + } + q++; + } + } + + void jpeg_encoder::flush_output_buffer() + { + if (m_out_buf_left != JPGE_OUT_BUF_SIZE) + m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left); + m_pOut_buf = m_out_buf; + m_out_buf_left = JPGE_OUT_BUF_SIZE; + } + + void jpeg_encoder::put_bits(uint bits, uint len) + { + m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len))); + while (m_bits_in >= 8) + { + uint8 c; +#define JPGE_PUT_BYTE(c) { *m_pOut_buf++ = (c); if (--m_out_buf_left == 0) flush_output_buffer(); } + JPGE_PUT_BYTE(c = (uint8)((m_bit_buffer >> 16) & 0xFF)); + if (c == 0xFF) JPGE_PUT_BYTE(0); + m_bit_buffer <<= 8; + m_bits_in -= 8; + } + } + + void jpeg_encoder::code_coefficients_pass_one(int component_num) + { + if (component_num >= 3) return; // just to shut up static analysis + int i, run_len, nbits, temp1; + int16* src = m_coefficient_array; + uint32* dc_count = component_num ? m_huff_count[0 + 1] : m_huff_count[0 + 0], * ac_count = component_num ? m_huff_count[2 + 1] : m_huff_count[2 + 0]; + + temp1 = src[0] - m_last_dc_val[component_num]; + m_last_dc_val[component_num] = src[0]; + if (temp1 < 0) temp1 = -temp1; + + nbits = 0; + while (temp1) + { + nbits++; temp1 >>= 1; + } + + dc_count[nbits]++; + for (run_len = 0, i = 1; i < 64; i++) + { + if ((temp1 = m_coefficient_array[i]) == 0) + run_len++; + else + { + while (run_len >= 16) + { + ac_count[0xF0]++; + run_len -= 16; + } + if (temp1 < 0) temp1 = -temp1; + nbits = 1; + while (temp1 >>= 1) nbits++; + ac_count[(run_len << 4) + nbits]++; + run_len = 0; + } + } + if (run_len) ac_count[0]++; + } + + void jpeg_encoder::code_coefficients_pass_two(int component_num) + { + int i, j, run_len, nbits, temp1, temp2; + int16* pSrc = m_coefficient_array; + uint* codes[2]; + uint8* code_sizes[2]; + + if (component_num == 0) + { + codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0]; + code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0]; + } + else + { + codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1]; + code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1]; + } + + temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num]; + m_last_dc_val[component_num] = pSrc[0]; + + if (temp1 < 0) + { + temp1 = -temp1; temp2--; + } + + nbits = 0; + while (temp1) + { + nbits++; temp1 >>= 1; + } + + put_bits(codes[0][nbits], code_sizes[0][nbits]); + if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits); + + for (run_len = 0, i = 1; i < 64; i++) + { + if ((temp1 = m_coefficient_array[i]) == 0) + run_len++; + else + { + while (run_len >= 16) + { + put_bits(codes[1][0xF0], code_sizes[1][0xF0]); + run_len -= 16; + } + if ((temp2 = temp1) < 0) + { + temp1 = -temp1; + temp2--; + } + nbits = 1; + while (temp1 >>= 1) + nbits++; + j = (run_len << 4) + nbits; + put_bits(codes[1][j], code_sizes[1][j]); + put_bits(temp2 & ((1 << nbits) - 1), nbits); + run_len = 0; + } + } + if (run_len) + put_bits(codes[1][0], code_sizes[1][0]); + } + + void jpeg_encoder::code_block(int component_num) + { + DCT2D(m_sample_array); + load_quantized_coefficients(component_num); + if (m_pass_num == 1) + code_coefficients_pass_one(component_num); + else + code_coefficients_pass_two(component_num); + } + + void jpeg_encoder::process_mcu_row() + { + if (m_num_components == 1) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8_grey(i); code_block(0); + } + } + else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2); + } + } + else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) + { + for (int i = 0; i < m_mcus_per_row; i++) + { + load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); + load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0); + load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2); + } + } + } + + bool jpeg_encoder::terminate_pass_one() + { + optimize_huffman_table(0 + 0, DC_LUM_CODES); optimize_huffman_table(2 + 0, AC_LUM_CODES); + if (m_num_components > 1) + { + optimize_huffman_table(0 + 1, DC_CHROMA_CODES); optimize_huffman_table(2 + 1, AC_CHROMA_CODES); + } + return second_pass_init(); + } + + bool jpeg_encoder::terminate_pass_two() + { + put_bits(0x7F, 7); + flush_output_buffer(); + emit_marker(M_EOI); + m_pass_num++; // purposely bump up m_pass_num, for debugging + return true; + } + + bool jpeg_encoder::process_end_of_image() + { + if (m_mcu_y_ofs) + { + if (m_mcu_y_ofs < 16) // check here just to shut up static analysis + { + for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) + memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu); + } + + process_mcu_row(); + } + + if (m_pass_num == 1) + return terminate_pass_one(); + else + return terminate_pass_two(); + } + + void jpeg_encoder::load_mcu(const void* pSrc) + { + const uint8* Psrc = reinterpret_cast(pSrc); + + uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst + + if (m_num_components == 1) + { + if (m_image_bpp == 4) + RGBA_to_Y(pDst, Psrc, m_image_x); + else if (m_image_bpp == 3) + RGB_to_Y(pDst, Psrc, m_image_x); + else + memcpy(pDst, Psrc, m_image_x); + } + else + { + if (m_image_bpp == 4) + RGBA_to_YCC(pDst, Psrc, m_image_x); + else if (m_image_bpp == 3) + RGB_to_YCC(pDst, Psrc, m_image_x); + else + Y_to_YCC(pDst, Psrc, m_image_x); + } + + // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16 + if (m_num_components == 1) + memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x); + else + { + const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2]; + uint8* q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt; + for (int i = m_image_x; i < m_image_x_mcu; i++) + { + *q++ = y; *q++ = cb; *q++ = cr; + } + } + + if (++m_mcu_y_ofs == m_mcu_y) + { + process_mcu_row(); + m_mcu_y_ofs = 0; + } + } + + void jpeg_encoder::clear() + { + m_mcu_lines[0] = NULL; + m_pass_num = 0; + m_all_stream_writes_succeeded = true; + } + + jpeg_encoder::jpeg_encoder() + { + clear(); + } + + jpeg_encoder::~jpeg_encoder() + { + deinit(); + } + + bool jpeg_encoder::init(output_stream* pStream, int width, int height, int src_channels, const params& comp_params) + { + deinit(); + if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false; + m_pStream = pStream; + m_params = comp_params; + return jpg_open(width, height, src_channels); + } + + void jpeg_encoder::deinit() + { + jpge_free(m_mcu_lines[0]); + clear(); + } + + bool jpeg_encoder::process_scanline(const void* pScanline) + { + if ((m_pass_num < 1) || (m_pass_num > 2)) return false; + if (m_all_stream_writes_succeeded) + { + if (!pScanline) + { + if (!process_end_of_image()) return false; + } + else + { + load_mcu(pScanline); + } + } + return m_all_stream_writes_succeeded; + } + + // Higher level wrappers/examples (optional). +#include + + class cfile_stream : public output_stream + { + cfile_stream(const cfile_stream&); + cfile_stream& operator= (const cfile_stream&); + + FILE* m_pFile; + bool m_bStatus; + + public: + cfile_stream() : m_pFile(NULL), m_bStatus(false) { } + + virtual ~cfile_stream() + { + close(); + } + + bool open(const char* pFilename) + { + close(); + m_pFile = fopen(pFilename, "wb"); + m_bStatus = (m_pFile != NULL); + return m_bStatus; + } + + bool close() + { + if (m_pFile) + { + if (fclose(m_pFile) == EOF) + { + m_bStatus = false; + } + m_pFile = NULL; + } + return m_bStatus; + } + + virtual bool put_buf(const void* pBuf, int len) + { + m_bStatus = m_bStatus && (fwrite(pBuf, len, 1, m_pFile) == 1); + return m_bStatus; + } + + uint get_size() const + { + return m_pFile ? ftell(m_pFile) : 0; + } + }; + + // Writes JPEG image to file. + bool compress_image_to_jpeg_file(const char* pFilename, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params) + { + cfile_stream dst_stream; + if (!dst_stream.open(pFilename)) + return false; + + jpge::jpeg_encoder dst_image; + if (!dst_image.init(&dst_stream, width, height, num_channels, comp_params)) + return false; + + for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++) + { + for (int i = 0; i < height; i++) + { + const uint8* pBuf = pImage_data + i * width * num_channels; + if (!dst_image.process_scanline(pBuf)) + return false; + } + if (!dst_image.process_scanline(NULL)) + return false; + } + + dst_image.deinit(); + + return dst_stream.close(); + } + + class memory_stream : public output_stream + { + memory_stream(const memory_stream&); + memory_stream& operator= (const memory_stream&); + + uint8* m_pBuf; + uint m_buf_size, m_buf_ofs; + + public: + memory_stream(void* pBuf, uint buf_size) : m_pBuf(static_cast(pBuf)), m_buf_size(buf_size), m_buf_ofs(0) { } + + virtual ~memory_stream() { } + + virtual bool put_buf(const void* pBuf, int len) + { + uint buf_remaining = m_buf_size - m_buf_ofs; + if ((uint)len > buf_remaining) + return false; + memcpy(m_pBuf + m_buf_ofs, pBuf, len); + m_buf_ofs += len; + return true; + } + + uint get_size() const + { + return m_buf_ofs; + } + }; + + bool compress_image_to_jpeg_file_in_memory(void* pDstBuf, int& buf_size, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params) + { + if ((!pDstBuf) || (!buf_size)) + return false; + + memory_stream dst_stream(pDstBuf, buf_size); + + buf_size = 0; + + jpge::jpeg_encoder dst_image; + if (!dst_image.init(&dst_stream, width, height, num_channels, comp_params)) + return false; + + for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++) + { + for (int i = 0; i < height; i++) + { + const uint8* pScanline = pImage_data + i * width * num_channels; + if (!dst_image.process_scanline(pScanline)) + return false; + } + if (!dst_image.process_scanline(NULL)) + return false; + } + + dst_image.deinit(); + + buf_size = dst_stream.get_size(); + return true; + } + +} // namespace jpge diff --git a/src/jpeg-compressor/jpge.h b/src/jpeg-compressor/jpge.h new file mode 100644 index 0000000000..b98a4a6413 --- /dev/null +++ b/src/jpeg-compressor/jpge.h @@ -0,0 +1,173 @@ +// jpge.h - C++ class for JPEG compression. +// Public Domain or Apache 2.0, Richard Geldreich +// Alex Evans: Added RGBA support, linear memory allocator. +#ifndef JPEG_ENCODER_H +#define JPEG_ENCODER_H + +namespace jpge +{ + typedef unsigned char uint8; + typedef signed short int16; + typedef signed int int32; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned int uint; + + // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common. + enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; + + // JPEG compression parameters structure. + struct params + { + inline params() : m_quality(85), m_subsampling(H2V2), m_no_chroma_discrim_flag(false), m_two_pass_flag(false), m_use_std_tables(false) { } + + inline bool check() const + { + if ((m_quality < 1) || (m_quality > 100)) return false; + if ((uint)m_subsampling > (uint)H2V2) return false; + return true; + } + + // Quality: 1-100, higher is better. Typical values are around 50-95. + int m_quality; + + // m_subsampling: + // 0 = Y (grayscale) only + // 1 = YCbCr, no subsampling (H1V1, YCbCr 1x1x1, 3 blocks per MCU) + // 2 = YCbCr, H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU) + // 3 = YCbCr, H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common) + subsampling_t m_subsampling; + + // Disables CbCr discrimination - only intended for testing. + // If true, the Y quantization table is also used for the CbCr channels. + bool m_no_chroma_discrim_flag; + + bool m_two_pass_flag; + + // By default we use the same quantization tables as mozjpeg's default. + // Set to true to use the traditional tables from JPEG Annex K. + bool m_use_std_tables; + }; + + // Writes JPEG image to a file. + // num_channels must be 1 (Y) or 3 (RGB), image pitch must be width*num_channels. + bool compress_image_to_jpeg_file(const char* pFilename, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params = params()); + + // Writes JPEG image to memory buffer. + // On entry, buf_size is the size of the output buffer pointed at by pBuf, which should be at least ~1024 bytes. + // If return value is true, buf_size will be set to the size of the compressed data. + bool compress_image_to_jpeg_file_in_memory(void* pBuf, int& buf_size, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params = params()); + + // Output stream abstract class - used by the jpeg_encoder class to write to the output stream. + // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts. + class output_stream + { + public: + virtual ~output_stream() { }; + virtual bool put_buf(const void* Pbuf, int len) = 0; + template inline bool put_obj(const T& obj) { return put_buf(&obj, sizeof(T)); } + }; + + // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions. + class jpeg_encoder + { + public: + jpeg_encoder(); + ~jpeg_encoder(); + + // Initializes the compressor. + // pStream: The stream object to use for writing compressed data. + // params - Compression parameters structure, defined above. + // width, height - Image dimensions. + // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data. + // Returns false on out of memory or if a stream write fails. + bool init(output_stream* pStream, int width, int height, int src_channels, const params& comp_params = params()); + + const params& get_params() const { return m_params; } + + // Deinitializes the compressor, freeing any allocated memory. May be called at any time. + void deinit(); + + uint get_total_passes() const { return m_params.m_two_pass_flag ? 2 : 1; } + inline uint get_cur_pass() { return m_pass_num; } + + // Call this method with each source scanline. + // width * src_channels bytes per scanline is expected (RGB or Y format). + // You must call with NULL after all scanlines are processed to finish compression. + // Returns false on out of memory or if a stream write fails. + bool process_scanline(const void* pScanline); + + private: + jpeg_encoder(const jpeg_encoder&); + jpeg_encoder& operator =(const jpeg_encoder&); + + typedef int32 sample_array_t; + + output_stream* m_pStream; + params m_params; + uint8 m_num_components; + uint8 m_comp_h_samp[3], m_comp_v_samp[3]; + int m_image_x, m_image_y, m_image_bpp, m_image_bpl; + int m_image_x_mcu, m_image_y_mcu; + int m_image_bpl_xlt, m_image_bpl_mcu; + int m_mcus_per_row; + int m_mcu_x, m_mcu_y; + uint8* m_mcu_lines[16]; + uint8 m_mcu_y_ofs; + sample_array_t m_sample_array[64]; + int16 m_coefficient_array[64]; + int32 m_quantization_tables[2][64]; + uint m_huff_codes[4][256]; + uint8 m_huff_code_sizes[4][256]; + uint8 m_huff_bits[4][17]; + uint8 m_huff_val[4][256]; + uint32 m_huff_count[4][256]; + int m_last_dc_val[3]; + enum { JPGE_OUT_BUF_SIZE = 2048 }; + uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; + uint8* m_pOut_buf; + uint m_out_buf_left; + uint32 m_bit_buffer; + uint m_bits_in; + uint8 m_pass_num; + bool m_all_stream_writes_succeeded; + + void optimize_huffman_table(int table_num, int table_len); + void emit_byte(uint8 i); + void emit_word(uint i); + void emit_marker(int marker); + void emit_jfif_app0(); + void emit_dqt(); + void emit_sof(); + void emit_dht(uint8* bits, uint8* val, int index, bool ac_flag); + void emit_dhts(); + void emit_sos(); + void emit_markers(); + void compute_huffman_table(uint* codes, uint8* code_sizes, uint8* bits, uint8* val); + void compute_quant_table(int32* dst, int16* src); + void adjust_quant_table(int32* dst, int32* src); + void first_pass_init(); + bool second_pass_init(); + bool jpg_open(int p_x_res, int p_y_res, int src_channels); + void load_block_8_8_grey(int x); + void load_block_8_8(int x, int y, int c); + void load_block_16_8(int x, int c); + void load_block_16_8_8(int x, int c); + void load_quantized_coefficients(int component_num); + void flush_output_buffer(); + void put_bits(uint bits, uint len); + void code_coefficients_pass_one(int component_num); + void code_coefficients_pass_two(int component_num); + void code_block(int component_num); + void process_mcu_row(); + bool terminate_pass_one(); + bool terminate_pass_two(); + bool process_end_of_image(); + void load_mcu(const void* src); + void clear(); + void init(); + }; + +} // namespace jpge + +#endif // JPEG_ENCODER diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d751969d56..6961432c11 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -365,6 +365,7 @@ target_link_libraries(libslic3r PNG::PNG ZLIB::ZLIB qoi + jpeg-compressor ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp index 655997be1a..855c32e698 100644 --- a/src/libslic3r/GCode/Thumbnails.cpp +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -2,6 +2,7 @@ #include "../miniz_extension.hpp" #include +#include namespace Slic3r::GCodeThumbnails { @@ -13,6 +14,12 @@ struct CompressedPNG : CompressedImageBuffer std::string_view tag() const override { return "thumbnail"sv; } }; +struct CompressedJPG : CompressedImageBuffer +{ + ~CompressedJPG() override { free(data); } + std::string_view tag() const override { return "thumbnail_JPG"sv; } +}; + struct CompressedQOI : CompressedImageBuffer { ~CompressedQOI() override { free(data); } @@ -28,9 +35,28 @@ std::unique_ptr compress_thumbnail_png(const ThumbnailDat std::unique_ptr compress_thumbnail_jpg(const ThumbnailData& data) { - //FIXME change to JPG - auto out = std::make_unique(); - out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1); + // Take vector of RGBA pixels and flip the image vertically + std::vector rgba_pixels(data.pixels.size()); + const size_t row_size = data.width * 4; + for (size_t y = 0; y < data.height; ++y) + ::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + + auto out = std::make_unique(); + + std::vector compressed_data(data.pixels.size()); + jpge::params params; + params.m_quality = 85; + params.m_subsampling = jpge::H2V2; + params.m_no_chroma_discrim_flag = false; + params.m_two_pass_flag = false; + params.m_use_std_tables = false; + + int compressed_data_size = int(compressed_data.size()); + if (jpge::compress_image_to_jpeg_file_in_memory(compressed_data.data(), compressed_data_size, data.width, data.height, 4, rgba_pixels.data(), params)) { + out->data = malloc(compressed_data_size); + out->size = size_t(compressed_data_size); + ::memcpy(out->data, (const void*)compressed_data.data(), out->size); + } return out; } From eddcebf929c507b0a37c96b29a20f877e405dc55 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 16 Dec 2021 10:56:59 +0100 Subject: [PATCH 10/22] Fix repeated calls to FindTBB in module mode. fixes #6355 --- cmake/modules/FindTBB.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindTBB.cmake.in b/cmake/modules/FindTBB.cmake.in index a7eafa545f..49e405c184 100644 --- a/cmake/modules/FindTBB.cmake.in +++ b/cmake/modules/FindTBB.cmake.in @@ -293,7 +293,7 @@ if(NOT TBB_FOUND) # Create targets ################################## - if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND) + if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND AND NOT TARGET TBB::tbb) add_library(TBB::tbb UNKNOWN IMPORTED) set_target_properties(TBB::tbb PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}" From 7f153a55b36561ba53ab6ded7533debd416feaf8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 7 Jan 2022 10:15:16 +0100 Subject: [PATCH 11/22] SLA archiver implemented for svg output, switchable in config. new config is sla_archive_format as a string. WIP Get rid of SVG class, use manual svg creation Revert changes in SVG.hpp and SVG.cpp --- src/PrusaSlicer.cpp | 4 +- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Format/SL1.hpp | 19 +-- src/libslic3r/Format/SL1_SVG.cpp | 139 ++++++++++++++++++++ src/libslic3r/Format/SL1_SVG.hpp | 22 ++++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 5 + src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/SLAPrint.cpp | 21 +-- src/libslic3r/SLAPrint.hpp | 25 +++- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 4 +- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 9 +- src/slic3r/GUI/Tab.cpp | 4 + 13 files changed, 217 insertions(+), 40 deletions(-) create mode 100644 src/libslic3r/Format/SL1_SVG.cpp create mode 100644 src/libslic3r/Format/SL1_SVG.hpp diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2648fba9e2..5bde6c1288 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -498,8 +498,6 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; - SL1Archive sla_archive(sla_print.printer_config()); - sla_print.set_printer(&sla_archive); sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { @@ -539,7 +537,7 @@ int CLI::run(int argc, char **argv) outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_archive.export_print(outfile_final, sla_print); + sla_print.export_print(outfile_final); } if (outfile != outfile_final) { if (Slic3r::rename_file(outfile, outfile_final)) { diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6961432c11..fff000da90 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -96,6 +96,8 @@ set(SLIC3R_SOURCES Format/STL.hpp Format/SL1.hpp Format/SL1.cpp + Format/SL1_SVG.hpp + Format/SL1_SVG.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp GCode/Thumbnails.cpp diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index 46a82e1b85..e6c0ff0896 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -15,27 +15,16 @@ protected: std::unique_ptr create_raster() const override; sla::RasterEncoder get_encoder() const override; + SLAPrinterConfig & cfg() { return m_cfg; } + const SLAPrinterConfig & cfg() const { return m_cfg; } + public: SL1Archive() = default; explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} - void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = ""); - void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "") - { - Zipper zipper(fname); - export_print(zipper, print, projectname); - } - - void apply(const SLAPrinterConfig &cfg) override - { - auto diff = m_cfg.diff(cfg); - if (!diff.empty()) { - m_cfg.apply_only(cfg, diff); - m_layers = {}; - } - } + void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "") override; }; ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp new file mode 100644 index 0000000000..bb2f94a68d --- /dev/null +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -0,0 +1,139 @@ +#include "SL1_SVG.hpp" +#include "SLA/RasterBase.hpp" +#include "libslic3r/LocalesUtils.hpp" + +namespace Slic3r { + +namespace { + +void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb) +{ + if (tr.flipXY) { + for (auto &p : ep.contour.points) std::swap(p.x(), p.y()); + for (auto &h : ep.holes) + for (auto &p : h.points) std::swap(p.x(), p.y()); + } + + if (tr.mirror_x){ + for (auto &p : ep.contour.points) p.x() = bb.max.x() - p.x() + bb.min.x(); + for (auto &h : ep.holes) + for (auto &p : h.points) p.x() = bb.max.x() - p.x() + bb.min.x(); + } + + if (tr.mirror_y){ + for (auto &p : ep.contour.points) p.y() = bb.max.y() - p.y() + bb.min.y(); + for (auto &h : ep.holes) + for (auto &p : h.points) p.y() = bb.max.y() - p.y() + bb.min.y(); + } +} + +void append_svg(std::string &buf, const Polygon &poly) +{ + buf += "(p.x())); + buf += " "; + buf += float_to_string_decimal_point(unscaled(p.y())); + } + buf += " z\""; // mark path as closed + buf += " />\n"; +} + +} // namespace + +// A fake raster from SVG +class SVGRaster : public sla::RasterBase { + // Resolution here will be used for svg boundaries + BoundingBox m_bb; + Trafo m_trafo; + + std::string m_svg; + +public: + SVGRaster(Resolution res = {}, Trafo tr = {}) + : m_bb{BoundingBox{{0, 0}, Vec2crd{res.width_px, res.height_px}}} + , m_trafo{tr} + { + std::string w = float_to_string_decimal_point(unscaled(res.width_px)); + std::string h = float_to_string_decimal_point(unscaled(res.height_px)); + // Add svg header. + m_svg = + "\n" + "\n" + "\n"; + + // Add black background; + m_svg += "\n"; + } + + void draw(const ExPolygon& poly) override + { + auto cpoly = poly; + + transform(cpoly, m_trafo, m_bb); + append_svg(m_svg, cpoly.contour); + for (auto &h : cpoly.holes) + append_svg(m_svg, h); + } + + Resolution resolution() const override + { + return {size_t(m_bb.size().x()), size_t(m_bb.size().y())}; + } + + // Pixel dimension is undefined in this case. + PixelDim pixel_dimensions() const override { return {0, 0}; } + + Trafo trafo() const override { return m_trafo; } + + sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override + { + std::vector data; + constexpr const char finish[] = "\n"; + + data.reserve(m_svg.size() + std::size(finish)); + + std::copy(m_svg.begin(), m_svg.end(), std::back_inserter(data)); + std::copy(finish, finish + std::size(finish) - 1, std::back_inserter(data)); + + return sla::EncodedRaster{std::move(data), "svg"}; + } +}; + +std::unique_ptr SL1_SVGArchive::create_raster() const +{ + auto w = scaled(cfg().display_width.getFloat()); + auto h = scaled(cfg().display_height.getFloat()); + + std::array mirror; + + mirror[X] = cfg().display_mirror_x.getBool(); + mirror[Y] = cfg().display_mirror_y.getBool(); + + auto ro = cfg().display_orientation.getInt(); + sla::RasterBase::Orientation orientation = + ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : + sla::RasterBase::roLandscape; + + if (orientation == sla::RasterBase::roPortrait) { + std::swap(w, h); + } + + sla::RasterBase::Resolution res{w, h}; + sla::RasterBase::Trafo tr{orientation, mirror}; + + // Gamma does not really make sense in an svg, right? + // double gamma = cfg().gamma_correction.getFloat(); + + return std::make_unique(SVGRaster::Resolution{w, h}, tr); +} + +sla::RasterEncoder SL1_SVGArchive::get_encoder() const +{ + return nullptr; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp new file mode 100644 index 0000000000..a3afbcdfff --- /dev/null +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -0,0 +1,22 @@ +#ifndef SL1_SVG_HPP +#define SL1_SVG_HPP + +#include "SL1.hpp" + +namespace Slic3r { + +class SL1_SVGArchive: public SL1Archive { +protected: + + // Override the factory methods to produce svg instead of a real raster. + std::unique_ptr create_raster() const override; + sla::RasterEncoder get_encoder() const override; + +public: + + using SL1Archive::SL1Archive; +}; + +} // namespace Slic3r + +#endif // SL1_SVG_HPP diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f8361f1fd1..44995cce36 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -573,7 +573,7 @@ static std::vector s_Preset_sla_printer_options { "elefant_foot_min_width", "gamma_correction", "min_exposure_time", "max_exposure_time", - "min_initial_exposure_time", "max_initial_exposure_time", + "min_initial_exposure_time", "max_initial_exposure_time", "sla_archive_format", //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 173aa4cee0..f0926b0a4c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3800,6 +3800,11 @@ void PrintConfigDef::init_sla_params() def->enum_labels.push_back(L("Fast")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(slamsFast)); + + def = this->add("sla_archive_format", coString); + def->label = L("Format of the output SLA archive"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("SL1")); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 077969d615..07b0245369 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -980,6 +980,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, max_exposure_time)) ((ConfigOptionFloat, min_initial_exposure_time)) ((ConfigOptionFloat, max_initial_exposure_time)) + ((ConfigOptionString, sla_archive_format)) ) PRINT_CONFIG_CLASS_DERIVED_DEFINE0( diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 55acd38465..ddf3d1419b 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,6 +1,9 @@ #include "SLAPrint.hpp" #include "SLAPrintSteps.hpp" +#include "Format/SL1.hpp" +#include "Format/SL1_SVG.hpp" + #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" @@ -240,8 +243,13 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con m_material_config.apply_only(config, material_diff, true); // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); - - if (m_printer) m_printer->apply(m_printer_config); + + if (!m_printer || std::find(printer_diff.begin(), printer_diff.end(), "sla_archive_format") != printer_diff.end()) { + if (m_printer_config.sla_archive_format.value == "SL1") + m_printer = std::make_unique(m_printer_config); + else if (m_printer_config.sla_archive_format.value == "SL2") + m_printer = std::make_unique(m_printer_config); + } struct ModelObjectStatus { enum Status { @@ -670,12 +678,6 @@ std::string SLAPrint::validate(std::string*) const return ""; } -void SLAPrint::set_printer(SLAArchive *arch) -{ - invalidate_step(slapsRasterize); - m_printer = arch; -} - bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); @@ -835,7 +837,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_ignore = { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0622bec4e5..58e090c4b8 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -399,8 +399,6 @@ protected: public: virtual ~SLAArchive() = default; - virtual void apply(const SLAPrinterConfig &cfg) = 0; - // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); template void draw_layers( @@ -422,6 +420,14 @@ public: }, execution::max_concurrency(ep)); } + + // Export the print into an archive using the provided zipper. + // TODO: Use an archive writer interface instead of Zipper. + // This is quite limiting as the Zipper is a complete class, not an interface. + // The output can only be a zip archive. + virtual void export_print(Zipper &zipper, + const SLAPrint &print, + const std::string &projectname = "") = 0; }; /** @@ -527,8 +533,17 @@ public: // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } - - void set_printer(SLAArchive *archiver); + + void export_print(Zipper &zipper, const std::string &projectname = "") + { + m_printer->export_print(zipper, *this, projectname); + } + + void export_print(const std::string &fname, const std::string &projectname = "") + { + Zipper zipper(fname); + export_print(zipper, projectname); + } private: @@ -550,7 +565,7 @@ private: std::vector m_printer_input; // The archive object which collects the raster images after slicing - SLAArchive *m_printer = nullptr; + std::unique_ptr m_printer; // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 5f7b4e8d3f..5d3d47c202 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -190,7 +190,7 @@ void BackgroundSlicingProcess::process_sla() ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); Zipper zipper(export_path); - m_sla_archive.export_print(zipper, *m_sla_print); // true, false, true, true); // renders also supports and pad + m_sla_print->export_print(zipper); for (const ThumbnailData& data : thumbnails) if (data.is_valid()) write_thumbnail(zipper, data); @@ -741,7 +741,7 @@ void BackgroundSlicingProcess::prepare_upload() ThumbnailsParams{current_print()->full_print_config().option("thumbnails")->values, true, true, true, true}); // true, false, true, true); // renders also supports and pad Zipper zipper{source_path.string()}; - m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); + m_sla_print->export_print(zipper, m_upload_job.upload_data.upload_path.string()); for (const ThumbnailData& data : thumbnails) if (data.is_valid()) write_thumbnail(zipper, data); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 5fba237e35..00a3ab6d05 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -11,7 +11,6 @@ #include "libslic3r/PrintBase.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" -#include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" @@ -84,7 +83,7 @@ public: ~BackgroundSlicingProcess(); void set_fff_print(Print *print) { m_fff_print = print; } - void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } + void set_sla_print(SLAPrint *print) { m_sla_print = print; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; } @@ -218,9 +217,9 @@ private: // Data structure, to which the G-code export writes its annotations. GCodeProcessorResult *m_gcode_result = nullptr; // Callback function, used to write thumbnails into gcode. - ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; - SL1Archive m_sla_archive; - // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. + ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + // Temporary G-code, there is one defined for the BackgroundSlicingProcess, + // differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, // but once set, it cannot be re-set. diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7d9277dd95..b4354c30e2 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2518,6 +2518,10 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("min_initial_exposure_time"); optgroup->append_single_option_line("max_initial_exposure_time"); + + optgroup = page->new_optgroup(L("Output")); + optgroup->append_single_option_line("sla_archive_format"); + build_print_host_upload_group(page.get()); const int notes_field_height = 25; // 250 From 00764ceadec5094ea0c85f3c8b49d8b49de6fc0d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 7 Jan 2022 13:12:40 +0100 Subject: [PATCH 12/22] Fix ignoring of changed sla printer params --- src/libslic3r/SLAPrint.cpp | 6 +++--- src/libslic3r/SLAPrint.hpp | 4 ++-- src/libslic3r/SLAPrintSteps.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index ddf3d1419b..746def7c45 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -244,11 +244,11 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); - if (!m_printer || std::find(printer_diff.begin(), printer_diff.end(), "sla_archive_format") != printer_diff.end()) { + if (!m_archiver || !printer_diff.empty()) { if (m_printer_config.sla_archive_format.value == "SL1") - m_printer = std::make_unique(m_printer_config); + m_archiver = std::make_unique(m_printer_config); else if (m_printer_config.sla_archive_format.value == "SL2") - m_printer = std::make_unique(m_printer_config); + m_archiver = std::make_unique(m_printer_config); } struct ModelObjectStatus { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 58e090c4b8..b2df0c4e92 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -536,7 +536,7 @@ public: void export_print(Zipper &zipper, const std::string &projectname = "") { - m_printer->export_print(zipper, *this, projectname); + m_archiver->export_print(zipper, *this, projectname); } void export_print(const std::string &fname, const std::string &projectname = "") @@ -565,7 +565,7 @@ private: std::vector m_printer_input; // The archive object which collects the raster images after slicing - std::unique_ptr m_printer; + std::unique_ptr m_archiver; // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fa7348781d..435e8c8e39 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1044,7 +1044,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { - if(canceled() || !m_print->m_printer) return; + if(canceled() || !m_print->m_archiver) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state @@ -1089,7 +1089,7 @@ void SLAPrint::Steps::rasterize() if(canceled()) return; // Print all the layers in parallel - m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn, + m_print->m_archiver->draw_layers(m_print->m_printer_input.size(), lvlfn, [this]() { return canceled(); }, ex_tbb); } From b45c6ef173b6709b9215f82c59218728f43bfe6a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 7 Jan 2022 13:18:40 +0100 Subject: [PATCH 13/22] Export scaled integer coordinates into svg This is faster and lossless --- src/libslic3r/Format/SL1_SVG.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index bb2f94a68d..70e3a444fe 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -32,9 +32,9 @@ void append_svg(std::string &buf, const Polygon &poly) buf += "(p.x())); + buf += std::to_string(p.x()); buf += " "; - buf += float_to_string_decimal_point(unscaled(p.y())); + buf += std::to_string(p.y()); } buf += " z\""; // mark path as closed buf += " />\n"; @@ -55,13 +55,24 @@ public: : m_bb{BoundingBox{{0, 0}, Vec2crd{res.width_px, res.height_px}}} , m_trafo{tr} { - std::string w = float_to_string_decimal_point(unscaled(res.width_px)); - std::string h = float_to_string_decimal_point(unscaled(res.height_px)); + // Inside the svg header, the boundaries will be defined in mm to + // the actual bed size. The viewport is then defined to work with our + // scaled coordinates. All the exported polygons will be in these scaled + // coordinates but svg rendering software will interpret them correctly + // in mm due to the header's definition. + std::string wf = float_to_string_decimal_point(unscaled(res.width_px)); + std::string hf = float_to_string_decimal_point(unscaled(res.height_px)); + std::string w = std::to_string(res.width_px); + std::string h = std::to_string(res.height_px); + + // Notice the header also defines the fill-rule as nonzero which should + // generate correct results for our ExPolygons. + // Add svg header. m_svg = "\n" "\n" - "\n"; @@ -128,7 +139,7 @@ std::unique_ptr SL1_SVGArchive::create_raster() const // Gamma does not really make sense in an svg, right? // double gamma = cfg().gamma_correction.getFloat(); - return std::make_unique(SVGRaster::Resolution{w, h}, tr); + return std::make_unique(res, tr); } sla::RasterEncoder SL1_SVGArchive::get_encoder() const From 72da90d28f1d63e84bd23f13a32edf9049489ec0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jan 2022 13:54:18 +0100 Subject: [PATCH 14/22] WIP wip --- src/libslic3r/Format/SL1.cpp | 8 +- src/libslic3r/Format/SL1_SVG.cpp | 106 +++++++++++++++++------ src/libslic3r/SLA/AGGRaster.hpp | 28 +++--- src/libslic3r/SLA/RasterBase.cpp | 8 +- src/libslic3r/SLA/RasterBase.hpp | 54 ++++++------ tests/libslic3r/test_marchingsquares.cpp | 14 +-- tests/libslic3r/test_png_io.cpp | 4 +- tests/sla_print/sla_print_tests.cpp | 8 +- tests/sla_print/sla_test_utils.cpp | 6 +- tests/sla_print/sla_test_utils.hpp | 4 +- 10 files changed, 147 insertions(+), 93 deletions(-) diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index 6ed8c5ebed..6f1e95528a 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -446,8 +446,8 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print) std::unique_ptr SL1Archive::create_raster() const { - sla::RasterBase::Resolution res; - sla::RasterBase::PixelDim pxdim; + sla::Resolution res; + sla::PixelDim pxdim; std::array mirror; double w = m_cfg.display_width.getFloat(); @@ -468,8 +468,8 @@ std::unique_ptr SL1Archive::create_raster() const std::swap(pw, ph); } - res = sla::RasterBase::Resolution{pw, ph}; - pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + res = sla::Resolution{pw, ph}; + pxdim = sla::PixelDim{w / pw, h / ph}; sla::RasterBase::Trafo tr{orientation, mirror}; double gamma = m_cfg.gamma_correction.getFloat(); diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 70e3a444fe..8788577d36 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -1,6 +1,7 @@ #include "SL1_SVG.hpp" #include "SLA/RasterBase.hpp" #include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/ClipperUtils.hpp" namespace Slic3r { @@ -29,41 +30,79 @@ void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBo void append_svg(std::string &buf, const Polygon &poly) { - buf += "(c.x()), precision) + +// " " + float_to_string_decimal_point(unscaled(c.y()), precision) + " m"; + +// for (auto &p : poly) { +// auto d = p - c; +// if (d.squaredNorm() == 0) continue; +// buf += " "; +// buf += float_to_string_decimal_point(unscaled(p.x() - c.x()), precision); +// buf += " "; +// buf += float_to_string_decimal_point(unscaled(p.y() - c.y()), precision); +// c = p; +// } +// buf += " z\""; // mark path as closed +// buf += " />\n"; +//} + } // namespace // A fake raster from SVG class SVGRaster : public sla::RasterBase { // Resolution here will be used for svg boundaries - BoundingBox m_bb; - Trafo m_trafo; + BoundingBox m_bb; + sla::Resolution m_res; + Trafo m_trafo; + Vec2d m_sc; std::string m_svg; public: - SVGRaster(Resolution res = {}, Trafo tr = {}) - : m_bb{BoundingBox{{0, 0}, Vec2crd{res.width_px, res.height_px}}} + SVGRaster(const BoundingBox &svgarea, sla::Resolution res, Trafo tr = {}) + : m_bb{svgarea} + , m_res{res} , m_trafo{tr} + , m_sc{double(m_res.width_px) / m_bb.size().x(), double(m_res.height_px) / m_bb.size().y()} { // Inside the svg header, the boundaries will be defined in mm to // the actual bed size. The viewport is then defined to work with our // scaled coordinates. All the exported polygons will be in these scaled // coordinates but svg rendering software will interpret them correctly // in mm due to the header's definition. - std::string wf = float_to_string_decimal_point(unscaled(res.width_px)); - std::string hf = float_to_string_decimal_point(unscaled(res.height_px)); - std::string w = std::to_string(res.width_px); - std::string h = std::to_string(res.height_px); + std::string wf = float_to_string_decimal_point(unscaled(m_bb.size().x())); + std::string hf = float_to_string_decimal_point(unscaled(m_bb.size().y())); + std::string w = std::to_string(coord_t(m_res.width_px)); + std::string h = std::to_string(coord_t(m_res.height_px)); // Notice the header also defines the fill-rule as nonzero which should // generate correct results for our ExPolygons. @@ -84,21 +123,28 @@ public: { auto cpoly = poly; - transform(cpoly, m_trafo, m_bb); - append_svg(m_svg, cpoly.contour); - for (auto &h : cpoly.holes) - append_svg(m_svg, h); + double tol = std::min(m_bb.size().x() / double(m_res.width_px), + m_bb.size().y() / double(m_res.height_px)); + + ExPolygons cpolys = poly.simplify(tol / 4.); + + for (auto &cpoly : cpolys) { + transform(cpoly, m_trafo, m_bb); + + for (auto &p : cpoly.contour.points) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + + for (auto &h : cpoly.holes) + for (auto &p : h) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + + append_svg(m_svg, cpoly.contour); + for (auto &h : cpoly.holes) + append_svg(m_svg, h); + } } - Resolution resolution() const override - { - return {size_t(m_bb.size().x()), size_t(m_bb.size().y())}; - } - - // Pixel dimension is undefined in this case. - PixelDim pixel_dimensions() const override { return {0, 0}; } - - Trafo trafo() const override { return m_trafo; } + Trafo trafo() const override { return m_trafo; } sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override { @@ -116,8 +162,11 @@ public: std::unique_ptr SL1_SVGArchive::create_raster() const { - auto w = scaled(cfg().display_width.getFloat()); - auto h = scaled(cfg().display_height.getFloat()); + auto w = cfg().display_width.getFloat(); + auto h = cfg().display_height.getFloat(); + + auto res_x = size_t(cfg().display_pixels_x.getInt()); + auto res_y = size_t(cfg().display_pixels_y.getInt()); std::array mirror; @@ -133,13 +182,14 @@ std::unique_ptr SL1_SVGArchive::create_raster() const std::swap(w, h); } - sla::RasterBase::Resolution res{w, h}; + BoundingBox svgarea{{0, 0}, {scaled(w), scaled(h)}}; + sla::RasterBase::Trafo tr{orientation, mirror}; // Gamma does not really make sense in an svg, right? // double gamma = cfg().gamma_correction.getFloat(); - return std::make_unique(res, tr); + return std::make_unique(svgarea, sla::Resolution{res_x * 4, res_y * 4}, tr); } sla::RasterEncoder SL1_SVGArchive::get_encoder() const diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp index bc68cd3778..7c8e71c2ae 100644 --- a/src/libslic3r/SLA/AGGRaster.hpp +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -41,7 +41,7 @@ public: using TValue = typename TColor::value_type; using TPixel = typename PixelRenderer::pixel_type; using TRawBuffer = agg::rendering_buffer; - + protected: Resolution m_resolution; @@ -153,8 +153,8 @@ public: } Trafo trafo() const override { return m_trafo; } - Resolution resolution() const override { return m_resolution; } - PixelDim pixel_dimensions() const override + Resolution resolution() const { return m_resolution; } + PixelDim pixel_dimensions() const { return {SCALING_FACTOR / m_pxdim_scaled.w_mm, SCALING_FACTOR / m_pxdim_scaled.h_mm}; @@ -186,11 +186,15 @@ class RasterGrayscaleAA : public _RasterGrayscaleAA { using typename Base::TValue; public: template - RasterGrayscaleAA(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, - const RasterBase::Trafo & trafo, - GammaFn && fn) - : Base(res, pd, trafo, Colors::White, Colors::Black, + RasterGrayscaleAA(const Resolution &res, + const PixelDim &pd, + const RasterBase::Trafo &trafo, + GammaFn &&fn) + : Base(res, + pd, + trafo, + Colors::White, + Colors::Black, std::forward(fn)) {} @@ -208,10 +212,10 @@ public: class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { public: - RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, - const RasterBase::PixelDim & pd, - const RasterBase::Trafo & trafo, - double gamma = 1.) + RasterGrayscaleAAGammaPower(const Resolution &res, + const PixelDim &pd, + const RasterBase::Trafo &trafo, + double gamma = 1.) : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) {} }; diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index cc9aca0274..0b6c45eff3 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -68,10 +68,10 @@ EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, } std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, - double gamma, - const RasterBase::Trafo & tr) + const Resolution &res, + const PixelDim &pxdim, + double gamma, + const RasterBase::Trafo &tr) { std::unique_ptr rst; diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 6439830fe8..657fc865c2 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -31,6 +31,27 @@ public: const char * extension() const { return m_ext.c_str(); } }; +/// Type that represents a resolution in pixels. +struct Resolution { + size_t width_px = 0; + size_t height_px = 0; + + Resolution() = default; + Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } +}; + +/// Types that represents the dimension of a pixel in millimeters. +struct PixelDim { + double w_mm = 1.; + double h_mm = 1.; + + PixelDim() = default; + PixelDim(double px_width_mm, double px_height_mm) + : w_mm(px_width_mm), h_mm(px_height_mm) + {} +}; + using RasterEncoder = std::function; @@ -63,35 +84,14 @@ public: Point get_center() const { return {center_x, center_y}; } }; - /// Type that represents a resolution in pixels. - struct Resolution { - size_t width_px = 0; - size_t height_px = 0; - - Resolution() = default; - Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} - size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm = 1.; - double h_mm = 1.; - - PixelDim() = default; - PixelDim(double px_width_mm, double px_height_mm) - : w_mm(px_width_mm), h_mm(px_height_mm) - {} - }; - virtual ~RasterBase() = default; /// Draw a polygon with holes. virtual void draw(const ExPolygon& poly) = 0; /// Get the resolution of the raster. - virtual Resolution resolution() const = 0; - virtual PixelDim pixel_dimensions() const = 0; +// virtual Resolution resolution() const = 0; +// virtual PixelDim pixel_dimensions() const = 0; virtual Trafo trafo() const = 0; virtual EncodedRaster encode(RasterEncoder encoder) const = 0; @@ -109,10 +109,10 @@ std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); // If gamma is zero, thresholding will be performed which disables AA. std::unique_ptr create_raster_grayscale_aa( - const RasterBase::Resolution &res, - const RasterBase::PixelDim & pxdim, - double gamma = 1.0, - const RasterBase::Trafo & tr = {}); + const Resolution &res, + const PixelDim &pxdim, + double gamma = 1.0, + const RasterBase::Trafo &tr = {}); }} // namespace Slic3r::sla diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 3553697acd..32b137175a 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -20,17 +20,17 @@ using namespace Slic3r; -static double area(const sla::RasterBase::PixelDim &pxd) +static double area(const sla::PixelDim &pxd) { return pxd.w_mm * pxd.h_mm; } static Slic3r::sla::RasterGrayscaleAA create_raster( - const sla::RasterBase::Resolution &res, + const sla::Resolution &res, double disp_w = 100., double disp_h = 100.) { - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo; @@ -107,7 +107,7 @@ static void test_expolys(Rst && rst, svg.Close(); double max_rel_err = 0.1; - sla::RasterBase::PixelDim pxd = rst.pixel_dimensions(); + sla::PixelDim pxd = rst.pixel_dimensions(); double max_abs_err = area(pxd) * scaled(1.) * scaled(1.); BoundingBox ref_bb; @@ -175,7 +175,7 @@ TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { - sla::RasterBase::PixelDim pixdim{1, 1}; + sla::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)}; @@ -205,7 +205,7 @@ TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") { - sla::RasterBase::PixelDim pixdim{1, 1}; + sla::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; @@ -321,7 +321,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { std::vector layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh)); - sla::RasterBase::Resolution res{2560, 1440}; + sla::Resolution res{2560, 1440}; double disp_w = 120.96; double disp_h = 68.04; diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp index b4fcd6255d..e8229b7163 100644 --- a/tests/libslic3r/test_png_io.cpp +++ b/tests/libslic3r/test_png_io.cpp @@ -9,9 +9,9 @@ using namespace Slic3r; -static sla::RasterGrayscaleAA create_raster(const sla::RasterBase::Resolution &res) +static sla::RasterGrayscaleAA create_raster(const sla::Resolution &res) { - sla::RasterBase::PixelDim pixdim{1., 1.}; + sla::PixelDim pixdim{1., 1.}; auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)}); sla::RasterBase::Trafo trafo; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index db8c5e93ec..f7b0df339a 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -159,8 +159,8 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { // Default Prusa SL1 display parameters - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.); REQUIRE(raster.resolution().width_px == res.width_px); @@ -186,8 +186,8 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { double disp_w = 120., disp_h = 68.; - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; double gamma = 1.; sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1082df2007..d98a920372 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -307,8 +307,8 @@ void check_validity(const TriangleMesh &input_mesh, int flags) void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring) { double disp_w = 120., disp_h = 68.; - sla::RasterBase::Resolution res{2560, 1440}; - sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::Resolution res{2560, 1440}; + sla::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo{o, mirroring}; @@ -400,7 +400,7 @@ double raster_white_area(const sla::RasterGrayscaleAA &raster) return a; } -double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) +double predict_error(const ExPolygon &p, const sla::PixelDim &pd) { auto lines = p.lines(); double pix_err = pixel_area(FullWhite, pd) / 2.; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index 2264ad856c..7171914d43 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -175,7 +175,7 @@ void check_raster_transformations(sla::RasterBase::Orientation o, ExPolygon square_with_hole(double v); -inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) +inline double pixel_area(TPixel px, const sla::PixelDim &pxdim) { return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); } @@ -183,7 +183,7 @@ inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) double raster_white_area(const sla::RasterGrayscaleAA &raster); long raster_pxsum(const sla::RasterGrayscaleAA &raster); -double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); +double predict_error(const ExPolygon &p, const sla::PixelDim &pd); sla::SupportPoints calc_support_pts( const TriangleMesh & mesh, From 5e97778528464f431097407964ab476b76c8a566 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jan 2022 15:30:08 +0100 Subject: [PATCH 15/22] Added new param sla_output_precision in nanometers Adopted a fast and easy integer to string conversion --- src/libslic3r/Format/SL1_SVG.cpp | 88 +++++++++++++++++++------------- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 8 +++ src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/SLAPrint.cpp | 3 +- src/slic3r/GUI/Tab.cpp | 1 + 6 files changed, 66 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 8788577d36..52fad14736 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -3,10 +3,50 @@ #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/ClipperUtils.hpp" +#include +#include +#include + namespace Slic3r { namespace { +size_t constexpr coord_t_bufsize = 40; + +inline char const* decimal_from(coord_t snumber, char* buffer) +{ + std::make_unsigned_t number = 0; + + char* ret = buffer; + + if( snumber < 0 ) { + *buffer++ = '-'; + number = -snumber; + } else + number = snumber; + + if( number == 0 ) { + *buffer++ = '0'; + } else { + char* p_first = buffer; + while( number != 0 ) { + *buffer++ = '0' + number % 10; + number /= 10; + } + std::reverse( p_first, buffer ); + } + + *buffer = '\0'; + + return ret; +} + +inline std::string coord2str(coord_t crd) +{ + char buf[coord_t_bufsize]; + return decimal_from(crd, buf); +} + void transform(ExPolygon &ep, const sla::RasterBase::Trafo &tr, const BoundingBox &bb) { if (tr.flipXY) { @@ -35,46 +75,23 @@ void append_svg(std::string &buf, const Polygon &poly) auto c = poly.points.front(); - buf += "(c.x()), precision) + -// " " + float_to_string_decimal_point(unscaled(c.y()), precision) + " m"; - -// for (auto &p : poly) { -// auto d = p - c; -// if (d.squaredNorm() == 0) continue; -// buf += " "; -// buf += float_to_string_decimal_point(unscaled(p.x() - c.x()), precision); -// buf += " "; -// buf += float_to_string_decimal_point(unscaled(p.y() - c.y()), precision); -// c = p; -// } -// buf += " z\""; // mark path as closed -// buf += " />\n"; -//} - } // namespace // A fake raster from SVG @@ -101,8 +118,8 @@ public: // in mm due to the header's definition. std::string wf = float_to_string_decimal_point(unscaled(m_bb.size().x())); std::string hf = float_to_string_decimal_point(unscaled(m_bb.size().y())); - std::string w = std::to_string(coord_t(m_res.width_px)); - std::string h = std::to_string(coord_t(m_res.height_px)); + std::string w = coord2str(coord_t(m_res.width_px)); + std::string h = coord2str(coord_t(m_res.height_px)); // Notice the header also defines the fill-rule as nonzero which should // generate correct results for our ExPolygons. @@ -126,7 +143,7 @@ public: double tol = std::min(m_bb.size().x() / double(m_res.width_px), m_bb.size().y() / double(m_res.height_px)); - ExPolygons cpolys = poly.simplify(tol / 4.); + ExPolygons cpolys = poly.simplify(tol); for (auto &cpoly : cpolys) { transform(cpoly, m_trafo, m_bb); @@ -165,8 +182,10 @@ std::unique_ptr SL1_SVGArchive::create_raster() const auto w = cfg().display_width.getFloat(); auto h = cfg().display_height.getFloat(); - auto res_x = size_t(cfg().display_pixels_x.getInt()); - auto res_y = size_t(cfg().display_pixels_y.getInt()); +// auto res_x = size_t(cfg().display_pixels_x.getInt()); +// auto res_y = size_t(cfg().display_pixels_y.getInt()); + size_t res_x = std::round(scaled(w) / cfg().sla_output_precision.getFloat()); + size_t res_y = std::round(scaled(h) / cfg().sla_output_precision.getFloat()); std::array mirror; @@ -188,8 +207,7 @@ std::unique_ptr SL1_SVGArchive::create_raster() const // Gamma does not really make sense in an svg, right? // double gamma = cfg().gamma_correction.getFloat(); - - return std::make_unique(svgarea, sla::Resolution{res_x * 4, res_y * 4}, tr); + return std::make_unique(svgarea, sla::Resolution{res_x, res_y}, tr); } sla::RasterEncoder SL1_SVGArchive::get_encoder() const diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 44995cce36..ca1363da24 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -573,7 +573,7 @@ static std::vector s_Preset_sla_printer_options { "elefant_foot_min_width", "gamma_correction", "min_exposure_time", "max_exposure_time", - "min_initial_exposure_time", "max_initial_exposure_time", "sla_archive_format", + "min_initial_exposure_time", "max_initial_exposure_time", "sla_archive_format", "sla_output_precision", //FIXME the print host keys are left here just for conversion from the Printer preset to Physical Printer preset. "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f0926b0a4c..3070f865b2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3805,6 +3805,14 @@ void PrintConfigDef::init_sla_params() def->label = L("Format of the output SLA archive"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("SL1")); + + def = this->add("sla_output_precision", coFloat); + def->label = L("SLA output precision"); + def->tooltip = L("Minimum resolution in nanometers"); + def->sidetext = L("nm"); + def->min = 1; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1000)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 07b0245369..75079ee716 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -981,6 +981,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, min_initial_exposure_time)) ((ConfigOptionFloat, max_initial_exposure_time)) ((ConfigOptionString, sla_archive_format)) + ((ConfigOptionFloat, sla_output_precision)) ) PRINT_CONFIG_CLASS_DERIVED_DEFINE0( diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 746def7c45..7b78dfea2f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -838,7 +838,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_ignore = { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b4354c30e2..a20bdb8310 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2521,6 +2521,7 @@ void TabPrinter::build_sla() optgroup = page->new_optgroup(L("Output")); optgroup->append_single_option_line("sla_archive_format"); + optgroup->append_single_option_line("sla_output_precision"); build_print_host_upload_group(page.get()); From ca7668d858ddbf731456c78ea2dcae51731ce6ce Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Jan 2022 09:59:29 +0100 Subject: [PATCH 16/22] Change precision units to mm Add some perf optimization for svg output writing --- src/libslic3r/Format/SL1_SVG.cpp | 17 ++++++++++------- src/libslic3r/PrintConfig.cpp | 6 +++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 52fad14736..0ea2306119 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -13,7 +13,7 @@ namespace { size_t constexpr coord_t_bufsize = 40; -inline char const* decimal_from(coord_t snumber, char* buffer) +char const* decimal_from(coord_t snumber, char* buffer) { std::make_unsigned_t number = 0; @@ -75,17 +75,18 @@ void append_svg(std::string &buf, const Polygon &poly) auto c = poly.points.front(); -// char intbuf[coord_t_bufsize]; + char intbuf[coord_t_bufsize]; - buf += std::string(" SL1_SVGArchive::create_raster() const // auto res_x = size_t(cfg().display_pixels_x.getInt()); // auto res_y = size_t(cfg().display_pixels_y.getInt()); - size_t res_x = std::round(scaled(w) / cfg().sla_output_precision.getFloat()); - size_t res_y = std::round(scaled(h) / cfg().sla_output_precision.getFloat()); + float precision_nm = scaled(cfg().sla_output_precision.getFloat()); + size_t res_x = std::round(scaled(w) / precision_nm); + size_t res_y = std::round(scaled(h) / precision_nm); std::array mirror; @@ -199,6 +201,7 @@ std::unique_ptr SL1_SVGArchive::create_raster() const if (orientation == sla::RasterBase::roPortrait) { std::swap(w, h); + std::swap(res_x, res_y); } BoundingBox svgarea{{0, 0}, {scaled(w), scaled(h)}}; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3070f865b2..5a674da846 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3809,10 +3809,10 @@ void PrintConfigDef::init_sla_params() def = this->add("sla_output_precision", coFloat); def->label = L("SLA output precision"); def->tooltip = L("Minimum resolution in nanometers"); - def->sidetext = L("nm"); - def->min = 1; + def->sidetext = L("mm"); + def->min = SCALING_FACTOR; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(1000)); + def->set_default_value(new ConfigOptionFloat(0.001)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) From 7a092467a823be49c2e724fbb2d7d1916610179a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 3 Feb 2022 17:12:53 +0100 Subject: [PATCH 17/22] Disable overly detailed test outputs --- tests/catch_main.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/catch_main.hpp b/tests/catch_main.hpp index 5ab71fdd74..ca5b47da84 100644 --- a/tests/catch_main.hpp +++ b/tests/catch_main.hpp @@ -3,7 +3,7 @@ #define CATCH_CONFIG_EXTERNAL_INTERFACES #define CATCH_CONFIG_MAIN -#define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" +// #define CATCH_CONFIG_DEFAULT_REPORTER "verboseconsole" #include namespace Catch { From f6b4cbdc321a640f03471e2f5d988ee4e02026d4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 4 Feb 2022 08:16:48 +0100 Subject: [PATCH 18/22] Follow-up of 030f4601149704fb3213ef44ca9c9910a1548ed0 - compress_thumbnail_jpg() implemented using embedded libjpeg --- src/CMakeLists.txt | 1 - src/jpeg-compressor/CMakeLists.txt | 9 - src/jpeg-compressor/README.md | 15 - src/jpeg-compressor/jpge.cpp | 1076 ---------------------------- src/jpeg-compressor/jpge.h | 173 ----- src/libslic3r/CMakeLists.txt | 5 +- src/libslic3r/GCode/Thumbnails.cpp | 60 +- 7 files changed, 45 insertions(+), 1294 deletions(-) delete mode 100644 src/jpeg-compressor/CMakeLists.txt delete mode 100644 src/jpeg-compressor/README.md delete mode 100644 src/jpeg-compressor/jpge.cpp delete mode 100644 src/jpeg-compressor/jpge.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e5ee9b3ee8..ab1c7b9645 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,6 @@ add_subdirectory(semver) add_subdirectory(libigl) add_subdirectory(hints) add_subdirectory(qoi) -add_subdirectory(jpeg-compressor) # Adding libnest2d project for bin packing... add_subdirectory(libnest2d) diff --git a/src/jpeg-compressor/CMakeLists.txt b/src/jpeg-compressor/CMakeLists.txt deleted file mode 100644 index 7d404dc995..0000000000 --- a/src/jpeg-compressor/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# PrusaSlicer specific CMake - -cmake_minimum_required(VERSION 2.8.12) -project(jpeg-compressor) - -add_library(jpeg-compressor STATIC - jpge.h - jpge.cpp -) diff --git a/src/jpeg-compressor/README.md b/src/jpeg-compressor/README.md deleted file mode 100644 index 2722225e67..0000000000 --- a/src/jpeg-compressor/README.md +++ /dev/null @@ -1,15 +0,0 @@ -** jpeg-compressor is a C++ JPEG compression/fuzzed low-RAM JPEG decompression codec.** - -For more information go to https://github.com/richgel999/jpeg-compressor - -THIS DIRECTORY CONTAINS THE TWO FILES: - -jpge.h -jpge.cpp - -TAKEN FROM - -master branch - -ON 03 FEB 2022. - diff --git a/src/jpeg-compressor/jpge.cpp b/src/jpeg-compressor/jpge.cpp deleted file mode 100644 index 4bdc34a97c..0000000000 --- a/src/jpeg-compressor/jpge.cpp +++ /dev/null @@ -1,1076 +0,0 @@ -// jpge.cpp - C++ class for JPEG compression. Richard Geldreich -// Supports grayscale, H1V1, H2V1, and H2V2 chroma subsampling factors, one or two pass Huffman table optimization, libjpeg-style quality 1-100 quality factors. -// Also supports using luma quantization tables for chroma. -// -// Released under two licenses. You are free to choose which license you want: -// License 1: -// Public Domain -// -// License 2: -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// v1.01, Dec. 18, 2010 - Initial release -// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.) -// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc. -// Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03). -// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug. -// Code tweaks to fix VS2008 static code analysis warnings (all looked harmless). -// Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02. -// v1.05, March 25, 2020: Added Apache 2.0 alternate license - -#include "jpge.h" - -#include -#include -#include - -#define JPGE_MAX(a,b) (((a)>(b))?(a):(b)) -#define JPGE_MIN(a,b) (((a)<(b))?(a):(b)) - -namespace jpge { - - static inline void* jpge_malloc(size_t nSize) { return malloc(nSize); } - static inline void jpge_free(void* p) { free(p); } - - // Various JPEG enums and tables. - enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 }; - enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 }; - - static uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 }; - static int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 }; - static int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; - - // Table from http://www.imagemagick.org/discourse-server/viewtopic.php?f=22&t=20333&p=98008#p98008 - // This is mozjpeg's default table, in zag order. - static int16 s_alt_quant[64] = { 16,16,16,16,17,16,18,20,20,18,25,27,24,27,25,37,34,31,31,34,37,56,40,43,40,43,40,56,85,53,62,53,53,62,53,85,75,91,74,69,74,91,75,135,106,94,94,106,135,156,131,124,131,156,189,169,169,189,238,226,238,311,311,418 }; - - static uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; - static uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; - static uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; - static uint8 s_ac_lum_val[AC_LUM_CODES] = - { - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, - 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, - 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, - 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - }; - static uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; - static uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; - static uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; - static uint8 s_ac_chroma_val[AC_CHROMA_CODES] = - { - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, - 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, - 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, - 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, - 0xf9,0xfa - }; - - // Low-level helper functions. - template inline void clear_obj(T& obj) { memset(&obj, 0, sizeof(obj)); } - - const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329; - static inline uint8 clamp(int i) { if (static_cast(i) > 255U) { if (i < 0) i = 0; else if (i > 255) i = 255; } return static_cast(i); } - - static inline int left_shifti(int val, uint32 bits) - { - return static_cast(static_cast(val) << bits); - } - - static void RGB_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) - { - for (; num_pixels; pDst += 3, pSrc += 3, num_pixels--) - { - const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; - pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); - pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); - pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); - } - } - - static void RGB_to_Y(uint8* pDst, const uint8* pSrc, int num_pixels) - { - for (; num_pixels; pDst++, pSrc += 3, num_pixels--) - pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); - } - - static void RGBA_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) - { - for (; num_pixels; pDst += 3, pSrc += 4, num_pixels--) - { - const int r = pSrc[0], g = pSrc[1], b = pSrc[2]; - pDst[0] = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); - pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16)); - pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16)); - } - } - - static void RGBA_to_Y(uint8* pDst, const uint8* pSrc, int num_pixels) - { - for (; num_pixels; pDst++, pSrc += 4, num_pixels--) - pDst[0] = static_cast((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16); - } - - static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) - { - for (; num_pixels; pDst += 3, pSrc++, num_pixels--) { pDst[0] = pSrc[0]; pDst[1] = 128; pDst[2] = 128; } - } - - // Forward DCT - DCT derived from jfdctint. - enum { CONST_BITS = 13, ROW_BITS = 2 }; -#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n)) -#define DCT_MUL(var, c) (static_cast(var) * static_cast(c)) -#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \ - int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \ - int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \ - int32 u1 = DCT_MUL(t12 + t13, 4433); \ - s2 = u1 + DCT_MUL(t13, 6270); \ - s6 = u1 + DCT_MUL(t12, -15137); \ - u1 = t4 + t7; \ - int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \ - int32 z5 = DCT_MUL(u3 + u4, 9633); \ - t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \ - t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \ - u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \ - u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \ - u3 += z5; u4 += z5; \ - s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3; - - static void DCT2D(int32* p) - { - int32 c, * q = p; - for (c = 7; c >= 0; c--, q += 8) - { - int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7]; - DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); - q[0] = left_shifti(s0, ROW_BITS); q[1] = DCT_DESCALE(s1, CONST_BITS - ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS - ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS - ROW_BITS); - q[4] = left_shifti(s4, ROW_BITS); q[5] = DCT_DESCALE(s5, CONST_BITS - ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS - ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS - ROW_BITS); - } - for (q = p, c = 7; c >= 0; c--, q++) - { - int32 s0 = q[0 * 8], s1 = q[1 * 8], s2 = q[2 * 8], s3 = q[3 * 8], s4 = q[4 * 8], s5 = q[5 * 8], s6 = q[6 * 8], s7 = q[7 * 8]; - DCT1D(s0, s1, s2, s3, s4, s5, s6, s7); - q[0 * 8] = DCT_DESCALE(s0, ROW_BITS + 3); q[1 * 8] = DCT_DESCALE(s1, CONST_BITS + ROW_BITS + 3); q[2 * 8] = DCT_DESCALE(s2, CONST_BITS + ROW_BITS + 3); q[3 * 8] = DCT_DESCALE(s3, CONST_BITS + ROW_BITS + 3); - q[4 * 8] = DCT_DESCALE(s4, ROW_BITS + 3); q[5 * 8] = DCT_DESCALE(s5, CONST_BITS + ROW_BITS + 3); q[6 * 8] = DCT_DESCALE(s6, CONST_BITS + ROW_BITS + 3); q[7 * 8] = DCT_DESCALE(s7, CONST_BITS + ROW_BITS + 3); - } - } - - struct sym_freq { uint m_key, m_sym_index; }; - - // Radix sorts sym_freq[] array by 32-bit key m_key. Returns ptr to sorted values. - static inline sym_freq* radix_sort_syms(uint num_syms, sym_freq* pSyms0, sym_freq* pSyms1) - { - const uint cMaxPasses = 4; - uint32 hist[256 * cMaxPasses]; clear_obj(hist); - for (uint i = 0; i < num_syms; i++) { uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; hist[256 * 2 + ((freq >> 16) & 0xFF)]++; hist[256 * 3 + ((freq >> 24) & 0xFF)]++; } - sym_freq* pCur_syms = pSyms0, * pNew_syms = pSyms1; - uint total_passes = cMaxPasses; while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; - for (uint pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) - { - const uint32* pHist = &hist[pass << 8]; - uint offsets[256], cur_ofs = 0; - for (uint i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } - for (uint i = 0; i < num_syms; i++) - pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; - sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; - } - return pCur_syms; - } - - // calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. - static void calculate_minimum_redundancy(sym_freq* A, int n) - { - int root, leaf, next, avbl, used, dpth; - if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; } - A[0].m_key += A[1].m_key; root = 0; leaf = 2; - for (next = 1; next < n - 1; next++) - { - if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = next; } - else A[next].m_key = A[leaf++].m_key; - if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key += A[root].m_key; A[root++].m_key = next; } - else A[next].m_key += A[leaf++].m_key; - } - A[n - 2].m_key = 0; - for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1; - avbl = 1; used = dpth = 0; root = n - 2; next = n - 1; - while (avbl > 0) - { - while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; } - while (avbl > used) { A[next--].m_key = dpth; avbl--; } - avbl = 2 * used; dpth++; used = 0; - } - } - - // Limits canonical Huffman code table's max code size to max_code_size. - static void huffman_enforce_max_code_size(int* pNum_codes, int code_list_len, int max_code_size) - { - if (code_list_len <= 1) return; - - for (int i = max_code_size + 1; i <= MAX_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; - - uint32 total = 0; - for (int i = max_code_size; i > 0; i--) - total += (((uint32)pNum_codes[i]) << (max_code_size - i)); - - while (total != (1UL << max_code_size)) - { - pNum_codes[max_code_size]--; - for (int i = max_code_size - 1; i > 0; i--) - { - if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } - } - total--; - } - } - - // Generates an optimized offman table. - void jpeg_encoder::optimize_huffman_table(int table_num, int table_len) - { - sym_freq syms0[MAX_HUFF_SYMBOLS], syms1[MAX_HUFF_SYMBOLS]; - syms0[0].m_key = 1; syms0[0].m_sym_index = 0; // dummy symbol, assures that no valid code contains all 1's - int num_used_syms = 1; - const uint32* pSym_count = &m_huff_count[table_num][0]; - for (int i = 0; i < table_len; i++) - if (pSym_count[i]) { syms0[num_used_syms].m_key = pSym_count[i]; syms0[num_used_syms++].m_sym_index = i + 1; } - sym_freq* pSyms = radix_sort_syms(num_used_syms, syms0, syms1); - calculate_minimum_redundancy(pSyms, num_used_syms); - - // Count the # of symbols of each code size. - int num_codes[1 + MAX_HUFF_CODESIZE]; clear_obj(num_codes); - for (int i = 0; i < num_used_syms; i++) - num_codes[pSyms[i].m_key]++; - - const uint JPGE_CODE_SIZE_LIMIT = 16; // the maximum possible size of a JPEG Huffman code (valid range is [9,16] - 9 vs. 8 because of the dummy symbol) - huffman_enforce_max_code_size(num_codes, num_used_syms, JPGE_CODE_SIZE_LIMIT); - - // Compute m_huff_bits array, which contains the # of symbols per code size. - clear_obj(m_huff_bits[table_num]); - for (int i = 1; i <= (int)JPGE_CODE_SIZE_LIMIT; i++) - m_huff_bits[table_num][i] = static_cast(num_codes[i]); - - // Remove the dummy symbol added above, which must be in largest bucket. - for (int i = JPGE_CODE_SIZE_LIMIT; i >= 1; i--) - { - if (m_huff_bits[table_num][i]) { m_huff_bits[table_num][i]--; break; } - } - - // Compute the m_huff_val array, which contains the symbol indices sorted by code size (smallest to largest). - for (int i = num_used_syms - 1; i >= 1; i--) - m_huff_val[table_num][num_used_syms - 1 - i] = static_cast(pSyms[i].m_sym_index - 1); - } - - // JPEG marker generation. - void jpeg_encoder::emit_byte(uint8 i) - { - m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_obj(i); - } - - void jpeg_encoder::emit_word(uint i) - { - emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF)); - } - - void jpeg_encoder::emit_marker(int marker) - { - emit_byte(uint8(0xFF)); emit_byte(uint8(marker)); - } - - // Emit JFIF marker - void jpeg_encoder::emit_jfif_app0() - { - emit_marker(M_APP0); - emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); - emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */ - emit_byte(0); - emit_byte(1); /* Major version */ - emit_byte(1); /* Minor version */ - emit_byte(0); /* Density unit */ - emit_word(1); - emit_word(1); - emit_byte(0); /* No thumbnail image */ - emit_byte(0); - } - - // Emit quantization tables - void jpeg_encoder::emit_dqt() - { - for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++) - { - emit_marker(M_DQT); - emit_word(64 + 1 + 2); - emit_byte(static_cast(i)); - for (int j = 0; j < 64; j++) - emit_byte(static_cast(m_quantization_tables[i][j])); - } - } - - // Emit start of frame marker - void jpeg_encoder::emit_sof() - { - emit_marker(M_SOF0); /* baseline */ - emit_word(3 * m_num_components + 2 + 5 + 1); - emit_byte(8); /* precision */ - emit_word(m_image_y); - emit_word(m_image_x); - emit_byte(m_num_components); - for (int i = 0; i < m_num_components; i++) - { - emit_byte(static_cast(i + 1)); /* component ID */ - emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */ - emit_byte(i > 0); /* quant. table num */ - } - } - - // Emit Huffman table. - void jpeg_encoder::emit_dht(uint8* bits, uint8* val, int index, bool ac_flag) - { - emit_marker(M_DHT); - - int length = 0; - for (int i = 1; i <= 16; i++) - length += bits[i]; - - emit_word(length + 2 + 1 + 16); - emit_byte(static_cast(index + (ac_flag << 4))); - - for (int i = 1; i <= 16; i++) - emit_byte(bits[i]); - - for (int i = 0; i < length; i++) - emit_byte(val[i]); - } - - // Emit all Huffman tables. - void jpeg_encoder::emit_dhts() - { - emit_dht(m_huff_bits[0 + 0], m_huff_val[0 + 0], 0, false); - emit_dht(m_huff_bits[2 + 0], m_huff_val[2 + 0], 0, true); - if (m_num_components == 3) - { - emit_dht(m_huff_bits[0 + 1], m_huff_val[0 + 1], 1, false); - emit_dht(m_huff_bits[2 + 1], m_huff_val[2 + 1], 1, true); - } - } - - // emit start of scan - void jpeg_encoder::emit_sos() - { - emit_marker(M_SOS); - emit_word(2 * m_num_components + 2 + 1 + 3); - emit_byte(m_num_components); - for (int i = 0; i < m_num_components; i++) - { - emit_byte(static_cast(i + 1)); - if (i == 0) - emit_byte((0 << 4) + 0); - else - emit_byte((1 << 4) + 1); - } - emit_byte(0); /* spectral selection */ - emit_byte(63); - emit_byte(0); - } - - // Emit all markers at beginning of image file. - void jpeg_encoder::emit_markers() - { - emit_marker(M_SOI); - emit_jfif_app0(); - emit_dqt(); - emit_sof(); - emit_dhts(); - emit_sos(); - } - - // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays. - void jpeg_encoder::compute_huffman_table(uint* codes, uint8* code_sizes, uint8* bits, uint8* val) - { - int i, l, last_p, si; - uint8 huff_size[257]; - uint huff_code[257]; - uint code; - - int p = 0; - for (l = 1; l <= 16; l++) - for (i = 1; i <= bits[l]; i++) - huff_size[p++] = (char)l; - - huff_size[p] = 0; last_p = p; // write sentinel - - code = 0; si = huff_size[0]; p = 0; - - while (huff_size[p]) - { - while (huff_size[p] == si) - huff_code[p++] = code++; - code <<= 1; - si++; - } - - memset(codes, 0, sizeof(codes[0]) * 256); - memset(code_sizes, 0, sizeof(code_sizes[0]) * 256); - for (p = 0; p < last_p; p++) - { - codes[val[p]] = huff_code[p]; - code_sizes[val[p]] = huff_size[p]; - } - } - - // Quantization table generation. - void jpeg_encoder::compute_quant_table(int32* pDst, int16* pSrc) - { - int32 q; - if (m_params.m_quality < 50) - q = 5000 / m_params.m_quality; - else - q = 200 - m_params.m_quality * 2; - for (int i = 0; i < 64; i++) - { - int32 j = *pSrc++; j = (j * q + 50L) / 100L; - *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255); - } - } - - // Higher-level methods. - void jpeg_encoder::first_pass_init() - { - m_bit_buffer = 0; m_bits_in = 0; - memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0])); - m_mcu_y_ofs = 0; - m_pass_num = 1; - } - - bool jpeg_encoder::second_pass_init() - { - compute_huffman_table(&m_huff_codes[0 + 0][0], &m_huff_code_sizes[0 + 0][0], m_huff_bits[0 + 0], m_huff_val[0 + 0]); - compute_huffman_table(&m_huff_codes[2 + 0][0], &m_huff_code_sizes[2 + 0][0], m_huff_bits[2 + 0], m_huff_val[2 + 0]); - if (m_num_components > 1) - { - compute_huffman_table(&m_huff_codes[0 + 1][0], &m_huff_code_sizes[0 + 1][0], m_huff_bits[0 + 1], m_huff_val[0 + 1]); - compute_huffman_table(&m_huff_codes[2 + 1][0], &m_huff_code_sizes[2 + 1][0], m_huff_bits[2 + 1], m_huff_val[2 + 1]); - } - first_pass_init(); - emit_markers(); - m_pass_num = 2; - return true; - } - - bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels) - { - m_num_components = 3; - switch (m_params.m_subsampling) - { - case Y_ONLY: - { - m_num_components = 1; - m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; - m_mcu_x = 8; m_mcu_y = 8; - break; - } - case H1V1: - { - m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1; - m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; - m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; - m_mcu_x = 8; m_mcu_y = 8; - break; - } - case H2V1: - { - m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1; - m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; - m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; - m_mcu_x = 16; m_mcu_y = 8; - break; - } - case H2V2: - { - m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2; - m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1; - m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1; - m_mcu_x = 16; m_mcu_y = 16; - } - } - - m_image_x = p_x_res; m_image_y = p_y_res; - m_image_bpp = src_channels; - m_image_bpl = m_image_x * src_channels; - m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1)); - m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1)); - m_image_bpl_xlt = m_image_x * m_num_components; - m_image_bpl_mcu = m_image_x_mcu * m_num_components; - m_mcus_per_row = m_image_x_mcu / m_mcu_x; - - if ((m_mcu_lines[0] = static_cast(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) return false; - for (int i = 1; i < m_mcu_y; i++) - m_mcu_lines[i] = m_mcu_lines[i - 1] + m_image_bpl_mcu; - - if (m_params.m_use_std_tables) - { - compute_quant_table(m_quantization_tables[0], s_std_lum_quant); - compute_quant_table(m_quantization_tables[1], m_params.m_no_chroma_discrim_flag ? s_std_lum_quant : s_std_croma_quant); - } - else - { - compute_quant_table(m_quantization_tables[0], s_alt_quant); - memcpy(m_quantization_tables[1], m_quantization_tables[0], sizeof(m_quantization_tables[1])); - } - - m_out_buf_left = JPGE_OUT_BUF_SIZE; - m_pOut_buf = m_out_buf; - - if (m_params.m_two_pass_flag) - { - clear_obj(m_huff_count); - first_pass_init(); - } - else - { - memcpy(m_huff_bits[0 + 0], s_dc_lum_bits, 17); memcpy(m_huff_val[0 + 0], s_dc_lum_val, DC_LUM_CODES); - memcpy(m_huff_bits[2 + 0], s_ac_lum_bits, 17); memcpy(m_huff_val[2 + 0], s_ac_lum_val, AC_LUM_CODES); - memcpy(m_huff_bits[0 + 1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0 + 1], s_dc_chroma_val, DC_CHROMA_CODES); - memcpy(m_huff_bits[2 + 1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2 + 1], s_ac_chroma_val, AC_CHROMA_CODES); - if (!second_pass_init()) return false; // in effect, skip over the first pass - } - return m_all_stream_writes_succeeded; - } - - void jpeg_encoder::load_block_8_8_grey(int x) - { - uint8* pSrc; - sample_array_t* pDst = m_sample_array; - x <<= 3; - for (int i = 0; i < 8; i++, pDst += 8) - { - pSrc = m_mcu_lines[i] + x; - pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128; - pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128; - } - } - - void jpeg_encoder::load_block_8_8(int x, int y, int c) - { - uint8* pSrc; - sample_array_t* pDst = m_sample_array; - x = (x * (8 * 3)) + c; - y <<= 3; - for (int i = 0; i < 8; i++, pDst += 8) - { - pSrc = m_mcu_lines[y + i] + x; - pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128; - pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128; - } - } - - void jpeg_encoder::load_block_16_8(int x, int c) - { - uint8* pSrc1, * pSrc2; - sample_array_t* pDst = m_sample_array; - x = (x * (16 * 3)) + c; - for (int i = 0; i < 16; i += 2, pDst += 8) - { - pSrc1 = m_mcu_lines[i + 0] + x; - pSrc2 = m_mcu_lines[i + 1] + x; - pDst[0] = ((pSrc1[0 * 3] + pSrc1[1 * 3] + pSrc2[0 * 3] + pSrc2[1 * 3] + 2) >> 2) - 128; pDst[1] = ((pSrc1[2 * 3] + pSrc1[3 * 3] + pSrc2[2 * 3] + pSrc2[3 * 3] + 2) >> 2) - 128; - pDst[2] = ((pSrc1[4 * 3] + pSrc1[5 * 3] + pSrc2[4 * 3] + pSrc2[5 * 3] + 2) >> 2) - 128; pDst[3] = ((pSrc1[6 * 3] + pSrc1[7 * 3] + pSrc2[6 * 3] + pSrc2[7 * 3] + 2) >> 2) - 128; - pDst[4] = ((pSrc1[8 * 3] + pSrc1[9 * 3] + pSrc2[8 * 3] + pSrc2[9 * 3] + 2) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + 2) >> 2) - 128; - pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + 2) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + 2) >> 2) - 128; - } - } - - void jpeg_encoder::load_block_16_8_8(int x, int c) - { - uint8* pSrc1; - sample_array_t* pDst = m_sample_array; - x = (x * (16 * 3)) + c; - for (int i = 0; i < 8; i++, pDst += 8) - { - pSrc1 = m_mcu_lines[i + 0] + x; - pDst[0] = ((pSrc1[0 * 3] + pSrc1[1 * 3] + 1) >> 1) - 128; pDst[1] = ((pSrc1[2 * 3] + pSrc1[3 * 3] + 1) >> 1) - 128; - pDst[2] = ((pSrc1[4 * 3] + pSrc1[5 * 3] + 1) >> 1) - 128; pDst[3] = ((pSrc1[6 * 3] + pSrc1[7 * 3] + 1) >> 1) - 128; - pDst[4] = ((pSrc1[8 * 3] + pSrc1[9 * 3] + 1) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + 1) >> 1) - 128; - pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + 1) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + 1) >> 1) - 128; - } - } - - void jpeg_encoder::load_quantized_coefficients(int component_num) - { - int32* q = m_quantization_tables[component_num > 0]; - int16* pDst = m_coefficient_array; - for (int i = 0; i < 64; i++) - { - sample_array_t j = m_sample_array[s_zag[i]]; - if (j < 0) - { - if ((j = -j + (*q >> 1)) < *q) - *pDst++ = 0; - else - *pDst++ = static_cast(-(j / *q)); - } - else - { - if ((j = j + (*q >> 1)) < *q) - *pDst++ = 0; - else - *pDst++ = static_cast((j / *q)); - } - q++; - } - } - - void jpeg_encoder::flush_output_buffer() - { - if (m_out_buf_left != JPGE_OUT_BUF_SIZE) - m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left); - m_pOut_buf = m_out_buf; - m_out_buf_left = JPGE_OUT_BUF_SIZE; - } - - void jpeg_encoder::put_bits(uint bits, uint len) - { - m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len))); - while (m_bits_in >= 8) - { - uint8 c; -#define JPGE_PUT_BYTE(c) { *m_pOut_buf++ = (c); if (--m_out_buf_left == 0) flush_output_buffer(); } - JPGE_PUT_BYTE(c = (uint8)((m_bit_buffer >> 16) & 0xFF)); - if (c == 0xFF) JPGE_PUT_BYTE(0); - m_bit_buffer <<= 8; - m_bits_in -= 8; - } - } - - void jpeg_encoder::code_coefficients_pass_one(int component_num) - { - if (component_num >= 3) return; // just to shut up static analysis - int i, run_len, nbits, temp1; - int16* src = m_coefficient_array; - uint32* dc_count = component_num ? m_huff_count[0 + 1] : m_huff_count[0 + 0], * ac_count = component_num ? m_huff_count[2 + 1] : m_huff_count[2 + 0]; - - temp1 = src[0] - m_last_dc_val[component_num]; - m_last_dc_val[component_num] = src[0]; - if (temp1 < 0) temp1 = -temp1; - - nbits = 0; - while (temp1) - { - nbits++; temp1 >>= 1; - } - - dc_count[nbits]++; - for (run_len = 0, i = 1; i < 64; i++) - { - if ((temp1 = m_coefficient_array[i]) == 0) - run_len++; - else - { - while (run_len >= 16) - { - ac_count[0xF0]++; - run_len -= 16; - } - if (temp1 < 0) temp1 = -temp1; - nbits = 1; - while (temp1 >>= 1) nbits++; - ac_count[(run_len << 4) + nbits]++; - run_len = 0; - } - } - if (run_len) ac_count[0]++; - } - - void jpeg_encoder::code_coefficients_pass_two(int component_num) - { - int i, j, run_len, nbits, temp1, temp2; - int16* pSrc = m_coefficient_array; - uint* codes[2]; - uint8* code_sizes[2]; - - if (component_num == 0) - { - codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0]; - code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0]; - } - else - { - codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1]; - code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1]; - } - - temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num]; - m_last_dc_val[component_num] = pSrc[0]; - - if (temp1 < 0) - { - temp1 = -temp1; temp2--; - } - - nbits = 0; - while (temp1) - { - nbits++; temp1 >>= 1; - } - - put_bits(codes[0][nbits], code_sizes[0][nbits]); - if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits); - - for (run_len = 0, i = 1; i < 64; i++) - { - if ((temp1 = m_coefficient_array[i]) == 0) - run_len++; - else - { - while (run_len >= 16) - { - put_bits(codes[1][0xF0], code_sizes[1][0xF0]); - run_len -= 16; - } - if ((temp2 = temp1) < 0) - { - temp1 = -temp1; - temp2--; - } - nbits = 1; - while (temp1 >>= 1) - nbits++; - j = (run_len << 4) + nbits; - put_bits(codes[1][j], code_sizes[1][j]); - put_bits(temp2 & ((1 << nbits) - 1), nbits); - run_len = 0; - } - } - if (run_len) - put_bits(codes[1][0], code_sizes[1][0]); - } - - void jpeg_encoder::code_block(int component_num) - { - DCT2D(m_sample_array); - load_quantized_coefficients(component_num); - if (m_pass_num == 1) - code_coefficients_pass_one(component_num); - else - code_coefficients_pass_two(component_num); - } - - void jpeg_encoder::process_mcu_row() - { - if (m_num_components == 1) - { - for (int i = 0; i < m_mcus_per_row; i++) - { - load_block_8_8_grey(i); code_block(0); - } - } - else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1)) - { - for (int i = 0; i < m_mcus_per_row; i++) - { - load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2); - } - } - else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1)) - { - for (int i = 0; i < m_mcus_per_row; i++) - { - load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); - load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2); - } - } - else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2)) - { - for (int i = 0; i < m_mcus_per_row; i++) - { - load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0); - load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0); - load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2); - } - } - } - - bool jpeg_encoder::terminate_pass_one() - { - optimize_huffman_table(0 + 0, DC_LUM_CODES); optimize_huffman_table(2 + 0, AC_LUM_CODES); - if (m_num_components > 1) - { - optimize_huffman_table(0 + 1, DC_CHROMA_CODES); optimize_huffman_table(2 + 1, AC_CHROMA_CODES); - } - return second_pass_init(); - } - - bool jpeg_encoder::terminate_pass_two() - { - put_bits(0x7F, 7); - flush_output_buffer(); - emit_marker(M_EOI); - m_pass_num++; // purposely bump up m_pass_num, for debugging - return true; - } - - bool jpeg_encoder::process_end_of_image() - { - if (m_mcu_y_ofs) - { - if (m_mcu_y_ofs < 16) // check here just to shut up static analysis - { - for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) - memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu); - } - - process_mcu_row(); - } - - if (m_pass_num == 1) - return terminate_pass_one(); - else - return terminate_pass_two(); - } - - void jpeg_encoder::load_mcu(const void* pSrc) - { - const uint8* Psrc = reinterpret_cast(pSrc); - - uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst - - if (m_num_components == 1) - { - if (m_image_bpp == 4) - RGBA_to_Y(pDst, Psrc, m_image_x); - else if (m_image_bpp == 3) - RGB_to_Y(pDst, Psrc, m_image_x); - else - memcpy(pDst, Psrc, m_image_x); - } - else - { - if (m_image_bpp == 4) - RGBA_to_YCC(pDst, Psrc, m_image_x); - else if (m_image_bpp == 3) - RGB_to_YCC(pDst, Psrc, m_image_x); - else - Y_to_YCC(pDst, Psrc, m_image_x); - } - - // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16 - if (m_num_components == 1) - memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x); - else - { - const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2]; - uint8* q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt; - for (int i = m_image_x; i < m_image_x_mcu; i++) - { - *q++ = y; *q++ = cb; *q++ = cr; - } - } - - if (++m_mcu_y_ofs == m_mcu_y) - { - process_mcu_row(); - m_mcu_y_ofs = 0; - } - } - - void jpeg_encoder::clear() - { - m_mcu_lines[0] = NULL; - m_pass_num = 0; - m_all_stream_writes_succeeded = true; - } - - jpeg_encoder::jpeg_encoder() - { - clear(); - } - - jpeg_encoder::~jpeg_encoder() - { - deinit(); - } - - bool jpeg_encoder::init(output_stream* pStream, int width, int height, int src_channels, const params& comp_params) - { - deinit(); - if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false; - m_pStream = pStream; - m_params = comp_params; - return jpg_open(width, height, src_channels); - } - - void jpeg_encoder::deinit() - { - jpge_free(m_mcu_lines[0]); - clear(); - } - - bool jpeg_encoder::process_scanline(const void* pScanline) - { - if ((m_pass_num < 1) || (m_pass_num > 2)) return false; - if (m_all_stream_writes_succeeded) - { - if (!pScanline) - { - if (!process_end_of_image()) return false; - } - else - { - load_mcu(pScanline); - } - } - return m_all_stream_writes_succeeded; - } - - // Higher level wrappers/examples (optional). -#include - - class cfile_stream : public output_stream - { - cfile_stream(const cfile_stream&); - cfile_stream& operator= (const cfile_stream&); - - FILE* m_pFile; - bool m_bStatus; - - public: - cfile_stream() : m_pFile(NULL), m_bStatus(false) { } - - virtual ~cfile_stream() - { - close(); - } - - bool open(const char* pFilename) - { - close(); - m_pFile = fopen(pFilename, "wb"); - m_bStatus = (m_pFile != NULL); - return m_bStatus; - } - - bool close() - { - if (m_pFile) - { - if (fclose(m_pFile) == EOF) - { - m_bStatus = false; - } - m_pFile = NULL; - } - return m_bStatus; - } - - virtual bool put_buf(const void* pBuf, int len) - { - m_bStatus = m_bStatus && (fwrite(pBuf, len, 1, m_pFile) == 1); - return m_bStatus; - } - - uint get_size() const - { - return m_pFile ? ftell(m_pFile) : 0; - } - }; - - // Writes JPEG image to file. - bool compress_image_to_jpeg_file(const char* pFilename, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params) - { - cfile_stream dst_stream; - if (!dst_stream.open(pFilename)) - return false; - - jpge::jpeg_encoder dst_image; - if (!dst_image.init(&dst_stream, width, height, num_channels, comp_params)) - return false; - - for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++) - { - for (int i = 0; i < height; i++) - { - const uint8* pBuf = pImage_data + i * width * num_channels; - if (!dst_image.process_scanline(pBuf)) - return false; - } - if (!dst_image.process_scanline(NULL)) - return false; - } - - dst_image.deinit(); - - return dst_stream.close(); - } - - class memory_stream : public output_stream - { - memory_stream(const memory_stream&); - memory_stream& operator= (const memory_stream&); - - uint8* m_pBuf; - uint m_buf_size, m_buf_ofs; - - public: - memory_stream(void* pBuf, uint buf_size) : m_pBuf(static_cast(pBuf)), m_buf_size(buf_size), m_buf_ofs(0) { } - - virtual ~memory_stream() { } - - virtual bool put_buf(const void* pBuf, int len) - { - uint buf_remaining = m_buf_size - m_buf_ofs; - if ((uint)len > buf_remaining) - return false; - memcpy(m_pBuf + m_buf_ofs, pBuf, len); - m_buf_ofs += len; - return true; - } - - uint get_size() const - { - return m_buf_ofs; - } - }; - - bool compress_image_to_jpeg_file_in_memory(void* pDstBuf, int& buf_size, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params) - { - if ((!pDstBuf) || (!buf_size)) - return false; - - memory_stream dst_stream(pDstBuf, buf_size); - - buf_size = 0; - - jpge::jpeg_encoder dst_image; - if (!dst_image.init(&dst_stream, width, height, num_channels, comp_params)) - return false; - - for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++) - { - for (int i = 0; i < height; i++) - { - const uint8* pScanline = pImage_data + i * width * num_channels; - if (!dst_image.process_scanline(pScanline)) - return false; - } - if (!dst_image.process_scanline(NULL)) - return false; - } - - dst_image.deinit(); - - buf_size = dst_stream.get_size(); - return true; - } - -} // namespace jpge diff --git a/src/jpeg-compressor/jpge.h b/src/jpeg-compressor/jpge.h deleted file mode 100644 index b98a4a6413..0000000000 --- a/src/jpeg-compressor/jpge.h +++ /dev/null @@ -1,173 +0,0 @@ -// jpge.h - C++ class for JPEG compression. -// Public Domain or Apache 2.0, Richard Geldreich -// Alex Evans: Added RGBA support, linear memory allocator. -#ifndef JPEG_ENCODER_H -#define JPEG_ENCODER_H - -namespace jpge -{ - typedef unsigned char uint8; - typedef signed short int16; - typedef signed int int32; - typedef unsigned short uint16; - typedef unsigned int uint32; - typedef unsigned int uint; - - // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common. - enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 }; - - // JPEG compression parameters structure. - struct params - { - inline params() : m_quality(85), m_subsampling(H2V2), m_no_chroma_discrim_flag(false), m_two_pass_flag(false), m_use_std_tables(false) { } - - inline bool check() const - { - if ((m_quality < 1) || (m_quality > 100)) return false; - if ((uint)m_subsampling > (uint)H2V2) return false; - return true; - } - - // Quality: 1-100, higher is better. Typical values are around 50-95. - int m_quality; - - // m_subsampling: - // 0 = Y (grayscale) only - // 1 = YCbCr, no subsampling (H1V1, YCbCr 1x1x1, 3 blocks per MCU) - // 2 = YCbCr, H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU) - // 3 = YCbCr, H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common) - subsampling_t m_subsampling; - - // Disables CbCr discrimination - only intended for testing. - // If true, the Y quantization table is also used for the CbCr channels. - bool m_no_chroma_discrim_flag; - - bool m_two_pass_flag; - - // By default we use the same quantization tables as mozjpeg's default. - // Set to true to use the traditional tables from JPEG Annex K. - bool m_use_std_tables; - }; - - // Writes JPEG image to a file. - // num_channels must be 1 (Y) or 3 (RGB), image pitch must be width*num_channels. - bool compress_image_to_jpeg_file(const char* pFilename, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params = params()); - - // Writes JPEG image to memory buffer. - // On entry, buf_size is the size of the output buffer pointed at by pBuf, which should be at least ~1024 bytes. - // If return value is true, buf_size will be set to the size of the compressed data. - bool compress_image_to_jpeg_file_in_memory(void* pBuf, int& buf_size, int width, int height, int num_channels, const uint8* pImage_data, const params& comp_params = params()); - - // Output stream abstract class - used by the jpeg_encoder class to write to the output stream. - // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts. - class output_stream - { - public: - virtual ~output_stream() { }; - virtual bool put_buf(const void* Pbuf, int len) = 0; - template inline bool put_obj(const T& obj) { return put_buf(&obj, sizeof(T)); } - }; - - // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions. - class jpeg_encoder - { - public: - jpeg_encoder(); - ~jpeg_encoder(); - - // Initializes the compressor. - // pStream: The stream object to use for writing compressed data. - // params - Compression parameters structure, defined above. - // width, height - Image dimensions. - // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data. - // Returns false on out of memory or if a stream write fails. - bool init(output_stream* pStream, int width, int height, int src_channels, const params& comp_params = params()); - - const params& get_params() const { return m_params; } - - // Deinitializes the compressor, freeing any allocated memory. May be called at any time. - void deinit(); - - uint get_total_passes() const { return m_params.m_two_pass_flag ? 2 : 1; } - inline uint get_cur_pass() { return m_pass_num; } - - // Call this method with each source scanline. - // width * src_channels bytes per scanline is expected (RGB or Y format). - // You must call with NULL after all scanlines are processed to finish compression. - // Returns false on out of memory or if a stream write fails. - bool process_scanline(const void* pScanline); - - private: - jpeg_encoder(const jpeg_encoder&); - jpeg_encoder& operator =(const jpeg_encoder&); - - typedef int32 sample_array_t; - - output_stream* m_pStream; - params m_params; - uint8 m_num_components; - uint8 m_comp_h_samp[3], m_comp_v_samp[3]; - int m_image_x, m_image_y, m_image_bpp, m_image_bpl; - int m_image_x_mcu, m_image_y_mcu; - int m_image_bpl_xlt, m_image_bpl_mcu; - int m_mcus_per_row; - int m_mcu_x, m_mcu_y; - uint8* m_mcu_lines[16]; - uint8 m_mcu_y_ofs; - sample_array_t m_sample_array[64]; - int16 m_coefficient_array[64]; - int32 m_quantization_tables[2][64]; - uint m_huff_codes[4][256]; - uint8 m_huff_code_sizes[4][256]; - uint8 m_huff_bits[4][17]; - uint8 m_huff_val[4][256]; - uint32 m_huff_count[4][256]; - int m_last_dc_val[3]; - enum { JPGE_OUT_BUF_SIZE = 2048 }; - uint8 m_out_buf[JPGE_OUT_BUF_SIZE]; - uint8* m_pOut_buf; - uint m_out_buf_left; - uint32 m_bit_buffer; - uint m_bits_in; - uint8 m_pass_num; - bool m_all_stream_writes_succeeded; - - void optimize_huffman_table(int table_num, int table_len); - void emit_byte(uint8 i); - void emit_word(uint i); - void emit_marker(int marker); - void emit_jfif_app0(); - void emit_dqt(); - void emit_sof(); - void emit_dht(uint8* bits, uint8* val, int index, bool ac_flag); - void emit_dhts(); - void emit_sos(); - void emit_markers(); - void compute_huffman_table(uint* codes, uint8* code_sizes, uint8* bits, uint8* val); - void compute_quant_table(int32* dst, int16* src); - void adjust_quant_table(int32* dst, int32* src); - void first_pass_init(); - bool second_pass_init(); - bool jpg_open(int p_x_res, int p_y_res, int src_channels); - void load_block_8_8_grey(int x); - void load_block_8_8(int x, int y, int c); - void load_block_16_8(int x, int c); - void load_block_16_8_8(int x, int c); - void load_quantized_coefficients(int component_num); - void flush_output_buffer(); - void put_bits(uint bits, uint len); - void code_coefficients_pass_one(int component_num); - void code_coefficients_pass_two(int component_num); - void code_block(int component_num); - void process_mcu_row(); - bool terminate_pass_one(); - bool terminate_pass_two(); - bool process_end_of_image(); - void load_mcu(const void* src); - void clear(); - void init(); - }; - -} // namespace jpge - -#endif // JPEG_ENCODER diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6961432c11..77977b76f3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -346,6 +346,9 @@ encoding_check(libslic3r) target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0) target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS}) + +find_package(JPEG REQUIRED) + target_link_libraries(libslic3r libnest2d admesh @@ -364,8 +367,8 @@ target_link_libraries(libslic3r ${CMAKE_DL_LIBS} PNG::PNG ZLIB::ZLIB + JPEG::JPEG qoi - jpeg-compressor ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp index 855c32e698..8d70539b7c 100644 --- a/src/libslic3r/GCode/Thumbnails.cpp +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -2,7 +2,8 @@ #include "../miniz_extension.hpp" #include -#include +#include +#include namespace Slic3r::GCodeThumbnails { @@ -36,27 +37,48 @@ std::unique_ptr compress_thumbnail_png(const ThumbnailDat std::unique_ptr compress_thumbnail_jpg(const ThumbnailData& data) { // Take vector of RGBA pixels and flip the image vertically - std::vector rgba_pixels(data.pixels.size()); - const size_t row_size = data.width * 4; - for (size_t y = 0; y < data.height; ++y) + std::vector rgba_pixels(data.pixels.size()); + const unsigned int row_size = data.width * 4; + for (unsigned int y = 0; y < data.height; ++y) { ::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size); + } + + // Store pointers to scanlines start for later use + std::vector rows_ptrs; + rows_ptrs.reserve(data.height); + for (unsigned int y = 0; y < data.height; ++y) { + rows_ptrs.emplace_back(&rgba_pixels[y * row_size]); + } + + std::vector compressed_data(data.pixels.size()); + unsigned char* compressed_data_ptr = compressed_data.data(); + unsigned long compressed_data_size = data.pixels.size(); + + jpeg_error_mgr err; + jpeg_compress_struct info; + info.err = jpeg_std_error(&err); + jpeg_create_compress(&info); + jpeg_mem_dest(&info, &compressed_data_ptr, &compressed_data_size); + + info.image_width = data.width; + info.image_height = data.height; + info.input_components = 4; + info.in_color_space = JCS_EXT_RGBA; + + jpeg_set_defaults(&info); + jpeg_set_quality(&info, 85, TRUE); + jpeg_start_compress(&info, TRUE); + + jpeg_write_scanlines(&info, rows_ptrs.data(), data.height); + jpeg_finish_compress(&info); + jpeg_destroy_compress(&info); + + // FIXME -> Add error checking auto out = std::make_unique(); - - std::vector compressed_data(data.pixels.size()); - jpge::params params; - params.m_quality = 85; - params.m_subsampling = jpge::H2V2; - params.m_no_chroma_discrim_flag = false; - params.m_two_pass_flag = false; - params.m_use_std_tables = false; - - int compressed_data_size = int(compressed_data.size()); - if (jpge::compress_image_to_jpeg_file_in_memory(compressed_data.data(), compressed_data_size, data.width, data.height, 4, rgba_pixels.data(), params)) { - out->data = malloc(compressed_data_size); - out->size = size_t(compressed_data_size); - ::memcpy(out->data, (const void*)compressed_data.data(), out->size); - } + out->data = malloc(compressed_data_size); + out->size = size_t(compressed_data_size); + ::memcpy(out->data, (const void*)compressed_data.data(), out->size); return out; } From 30dc2bf39cf246b9e23641f5b860a4c291da4222 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 4 Feb 2022 09:52:00 +0100 Subject: [PATCH 19/22] Do not show ProjectDropDialog when drag and dropping a 3mf file produced by other softwares and the plater is not empty --- src/libslic3r/Format/3mf.cpp | 30 ++++++++++++++++++++++++++++++ src/libslic3r/Format/3mf.hpp | 3 +++ src/slic3r/GUI/Plater.cpp | 23 ++++++++++++++--------- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index ad428a7357..4259562aa8 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -3136,6 +3136,36 @@ static void handle_legacy_project_loaded(unsigned int version_project_file, Dyna } } +bool is_project_3mf(const std::string& filename) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return false; + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + // loop the entries to search for config + mz_zip_archive_file_stat stat; + bool config_found = false; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + config_found = true; + break; + } + } + } + + close_zip_reader(&archive); + + return config_found; +} + bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { if (path == nullptr || model == nullptr) diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index b91e90da74..5c2a2e672a 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -29,6 +29,9 @@ namespace Slic3r { class DynamicPrintConfig; struct ThumbnailData; + // Returns true if the 3mf file with the given filename is a PrusaSlicer project file (i.e. if it contains a config). + extern bool is_project_3mf(const std::string& filename); + // Load the content of a 3mf file into the given model and preset bundle. extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 54232ca874..32cd327a80 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5544,17 +5544,22 @@ bool Plater::load_files(const wxArrayString& filenames) if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) { LoadType load_type = LoadType::Unknown; if (!model().objects.empty()) { - if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { - ProjectDropDialog dlg(filename); - if (dlg.ShowModal() == wxID_OK) { - int choice = dlg.get_action(); - load_type = static_cast(choice); - wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) || + (boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) + load_type = LoadType::OpenProject; + else { + if (wxGetApp().app_config->get("show_drop_project_dialog") == "1") { + ProjectDropDialog dlg(filename); + if (dlg.ShowModal() == wxID_OK) { + int choice = dlg.get_action(); + load_type = static_cast(choice); + wxGetApp().app_config->set("drop_project_action", std::to_string(choice)); + } } + else + load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), + static_cast(LoadType::OpenProject), static_cast(LoadType::LoadConfig))); } - else - load_type = static_cast(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")), - static_cast(LoadType::OpenProject), static_cast(LoadType::LoadConfig))); } else load_type = LoadType::OpenProject; From 9585fda2f14659b2468d2b285916c3d29a2ff7f3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Feb 2022 10:01:48 +0100 Subject: [PATCH 20/22] UnsavedChangesDialog: Fixed a crash, when enum_labels wasn't defined for some enum config option. --- src/slic3r/GUI/UnsavedChangesDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index d49b4b1d34..c62977bbaf 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1030,7 +1030,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_s wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false) { const ConfigOptionDef& def = config.def()->options.at(opt_key); - const std::vector& names = def.enum_labels;//ConfigOptionEnum::get_enum_names(); + const std::vector& names = def.enum_labels.empty() ? def.enum_values : def.enum_labels; int val = config.option(opt_key)->getInt(); // Each infill doesn't use all list of infill declared in PrintConfig.hpp. From 6f595ceb648081081c5b3b5bc2969764c498ef27 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 4 Feb 2022 11:30:07 +0100 Subject: [PATCH 21/22] Fix for fff_print_tests --- src/libslic3r/PrintConfig.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 75079ee716..b97168e84e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -762,6 +762,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionInt, standby_temperature_delta)) ((ConfigOptionInts, temperature)) ((ConfigOptionInt, threads)) + ((ConfigOptionPoints, thumbnails)) + ((ConfigOptionEnum, thumbnails_format)) ((ConfigOptionBools, wipe)) ((ConfigOptionBool, wipe_tower)) ((ConfigOptionFloat, wipe_tower_x)) From b04e05b3f75838173c6c5453eea04f6e674cc361 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 4 Feb 2022 12:02:10 +0100 Subject: [PATCH 22/22] Fixed bug into GLTexture::load_from_svg_files_as_sprites_array() - One pixel of generated icons was cut away --- src/slic3r/GUI/GLTexture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index cbf760ff48..3b99397ad5 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -211,7 +211,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorwidth, image->height); // offset by 1 to leave the first pixel empty (both in x and y) - nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride); + nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px_ex, sprite_size_px_ex, sprite_stride); // makes white only copy of the sprite ::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes);