From 78128b186251837c4ec51100189b90c7880ba69a Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 14 Apr 2022 14:25:48 +0200 Subject: [PATCH] Experimental SL2 binary format by sorting polygons by TSP and storing ints by planes, RLE compressing zeros in MSB planes. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Format/SL1_Binary.cpp | 328 ++++++++++++++++++++++++++++ src/libslic3r/Format/SL1_Binary.hpp | 22 ++ src/libslic3r/Format/SLAArchive.cpp | 5 + src/libslic3r/Zipper.hpp | 2 +- src/slic3r/GUI/GUI_App.cpp | 2 +- 6 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 src/libslic3r/Format/SL1_Binary.cpp create mode 100644 src/libslic3r/Format/SL1_Binary.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 838af44fb7..10af670273 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -100,6 +100,8 @@ set(SLIC3R_SOURCES Format/SL1.cpp Format/SL1_SVG.hpp Format/SL1_SVG.cpp + Format/SL1_Binary.hpp + Format/SL1_Binary.cpp Format/pwmx.hpp Format/pwmx.cpp GCode/ThumbnailData.cpp diff --git a/src/libslic3r/Format/SL1_Binary.cpp b/src/libslic3r/Format/SL1_Binary.cpp new file mode 100644 index 0000000000..676f955852 --- /dev/null +++ b/src/libslic3r/Format/SL1_Binary.cpp @@ -0,0 +1,328 @@ +#include "SL1_Binary.hpp" +#include "SLA/RasterBase.hpp" +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/ShortestPath.hpp" + +#include +#include +#include +#include +using namespace std::literals; + +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(); + } +} + +struct BinaryWriter { + std::vector data; + + void store(unsigned char c) { data.emplace_back(c); } + void store(coord_t v) { + reserve_more(4); + auto *c = reinterpret_cast(&v); + for (int i = 0; i < 4; ++ i) + store(c[i]); + } + void store(uint32_t v) { + reserve_more(4); + auto *c = reinterpret_cast(&v); + for (int i = 0; i < 4; ++ i) + store(c[i]); + } + void store(float v) { + reserve_more(4); + auto *c = reinterpret_cast(&v); + for (int i = 0; i < 4; ++ i) + store(c[i]); + } + void store_plane(uint32_t v, int plane) { + auto *c = reinterpret_cast(&v); + store(c[plane]); + } + + void reserve_more(size_t more) { + if (data.capacity() < data.size() + more) + data.reserve(next_highest_power_of_2(data.size() + more)); + } +}; + +struct RLEPlaneWriter { + std::vector data; + + void store(unsigned char c) { data.emplace_back(c); } + + void store_plane(uint32_t v, int plane) { + auto *c = reinterpret_cast(&v); + store(c[plane]); + } + + void reserve_more(size_t more) { + if (data.capacity() < data.size() + more) + data.reserve(next_highest_power_of_2(data.size() + more)); + } + + // RLE compression, compressing zeros effectively. + void compress_to(BinaryWriter &out) { + for (int i = 0; i < data.size(); ) { + int j = i; + if (data[i] == 0) { + // Always prefer to emit zeros. How many are there? + for (; j < data.size() && data[j] == 0; ++ j) ; + // Store number of zeros minus 1. + out.store((unsigned char)(j - i - 1)); + } else { + // Store a chain of non-zeros. Count how many non-zeros should be emitted with a single prefix. + // Emitting a zero is free, however emitting non-zero requires a prefix, thus emitting some zeros + // inside a string of non-zeros may be cheaper. + int max = std::min(int(data.size()), i + 128); + // Skip all non-zeros, allow single zero exceptions. + for (; j < max && (data[j] != 0 || (j + 1 < max && data[j + 1] != 0)); ++ j) ; + // Store number of zeros prefixed with zero minus 1. + out.store((unsigned char)((1 << 7) + (j - i - 1))); + // Store the non-zero data (with single zero exceptions). + out.data.insert(out.data.end(), data.begin() + i, data.begin() + j); + } + i = j; + } + } +}; + +struct BitWriter { + std::vector data; + int size_last { 0 }; + + void store(bool v) { + if (size_last == 0) { + reserve_more(1); + data.emplace_back(0); + size_last = 8; + } + data.back() |= uint8_t(v) << (-- size_last); + } + + void reserve_more(size_t more) { + if (data.capacity() < data.size() + more) + data.reserve(next_highest_power_of_2(data.size() + more)); + } +}; + +} // namespace + +// A fake raster from SVG +class BinaryRaster : public sla::RasterBase { + // Resolution here will be used for svg boundaries + BoundingBox m_bb; + sla::Resolution m_res; + Trafo m_trafo; + Vec2d m_sc; + + mutable std::vector m_polygons; + + // Saves around 1% of data on a large SLA project by bringing the polygons + // close one to the other, thus reducing the difference when transitioning + // between polygons. + static void reorder_polygons(std::vector &polygons) + { + std::vector centers; + centers.reserve(polygons.size()); + for (const Points &pts : polygons) + centers.emplace_back(BoundingBox(pts).center()); + Point zero { 0, 0 }; + std::vector ordering = chain_points(centers, &zero); + std::vector sorted; + sorted.reserve(polygons.size()); + for (size_t i = 0; i < polygons.size(); ++ i) + sorted.emplace_back(std::move(polygons[ordering[i]])); + polygons = std::move(sorted); + } + +public: + BinaryRaster(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()} + { + } + + void draw(const ExPolygon& poly) override + { + auto cpoly = poly; + + 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); + + 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())}; + m_polygons.emplace_back(std::move(cpoly.contour.points)); + + for (auto &h : cpoly.holes) { + for (auto &p : h) + p = {std::round(p.x() * m_sc.x()), std::round(p.y() * m_sc.y())}; + m_polygons.emplace_back(std::move(h.points)); + } + } + } + + Trafo trafo() const override { return m_trafo; } + + sla::EncodedRaster encode(sla::RasterEncoder /*encoder*/) const override + { + reorder_polygons(m_polygons); + + BinaryWriter writer; + writer.store(unscaled(m_bb.size().x())); + writer.store(unscaled(m_bb.size().y())); + writer.store(uint32_t(m_res.width_px)); + writer.store(uint32_t(m_res.height_px)); + +#if 1 + static constexpr const int num_planes_raw = 1; + RLEPlaneWriter plane_writer; + + writer.store(uint32_t(m_polygons.size())); + for (int plane = 0; plane < num_planes_raw; ++ plane) + for (const Points &poly : m_polygons) + writer.store_plane(uint32_t(poly.size()), plane); + for (int plane = 0; plane < num_planes_raw; ++ plane) { + plane_writer.data.clear(); + for (const Points &poly : m_polygons) + plane_writer.store_plane(uint32_t(poly.size()), plane); + plane_writer.compress_to(writer); + } + + BitWriter signs; + { + coord_t prev = 0; + for (Points &poly : m_polygons) + for (Point &pt : poly) { + coord_t dif = pt.x() - prev; + prev = pt.x(); + signs.store(dif < 0); + pt.x() = std::abs(dif); + } + prev = 0; + for (Points &poly : m_polygons) + for (Point &pt : poly) { + coord_t dif = pt.y() - prev; + prev = pt.y(); + signs.store(dif < 0); + pt.y() = std::abs(dif); + } + } + append(writer.data, std::move(signs.data)); + + for (int plane = 0; plane < num_planes_raw; ++ plane) + for (const Points &poly : m_polygons) + for (const Point &pt : poly) + writer.store_plane(pt.x(), plane); + for (int plane = num_planes_raw; plane < 4; ++ plane) { + plane_writer.data.clear(); + for (const Points &poly : m_polygons) + for (const Point &pt : poly) + plane_writer.store_plane(pt.x(), plane); + plane_writer.compress_to(writer); + } + for (int plane = 0; plane < num_planes_raw; ++ plane) + for (const Points &poly : m_polygons) + for (const Point &pt : poly) + writer.store_plane(pt.y(), plane); + for (int plane = num_planes_raw; plane < 4; ++ plane) { + plane_writer.data.clear(); + for (const Points &poly : m_polygons) + for (const Point &pt : poly) + plane_writer.store_plane(pt.y(), plane); + plane_writer.compress_to(writer); + } +#else + Point prev { 0, 0 }; + for (Points &poly : m_polygons) + for (Point &pt : poly) { + Point dif = pt - prev; + prev = pt; + pt = dif; + } + + writer.store(uint32_t(m_polygons.size())); + for (const Points &poly : m_polygons) + writer.store(uint32_t(poly.size())); + + for (const Points &poly : m_polygons) + for (const Point &pt : poly) { + writer.store(pt.x()); + writer.store(pt.y()); + } +#endif + + return sla::EncodedRaster{std::move(writer.data), "bin"}; + } +}; + +std::unique_ptr SL1_BinaryArchive::create_raster() const +{ + auto w = cfg().display_width.getFloat(); + auto h = cfg().display_height.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; + + 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); + std::swap(res_x, res_y); + } + + 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(svgarea, sla::Resolution{res_x, res_y}, tr); +} + +sla::RasterEncoder SL1_BinaryArchive::get_encoder() const +{ + return nullptr; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1_Binary.hpp b/src/libslic3r/Format/SL1_Binary.hpp new file mode 100644 index 0000000000..23f8b889df --- /dev/null +++ b/src/libslic3r/Format/SL1_Binary.hpp @@ -0,0 +1,22 @@ +#ifndef SL1_BINARY_HPP +#define SL1_BINARY_HPP + +#include "SL1.hpp" + +namespace Slic3r { + +class SL1_BinaryArchive: 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_BINARY_HPP diff --git a/src/libslic3r/Format/SLAArchive.cpp b/src/libslic3r/Format/SLAArchive.cpp index 8e2bf2824f..ea62a4a262 100644 --- a/src/libslic3r/Format/SLAArchive.cpp +++ b/src/libslic3r/Format/SLAArchive.cpp @@ -1,6 +1,7 @@ #include "SLAArchive.hpp" #include "SL1.hpp" +#include "SL1_Binary.hpp" #include "SL1_SVG.hpp" #include "pwmx.hpp" @@ -29,6 +30,10 @@ static const std::map REGISTERED_ARCHIVES { "SL2", { "sl2", [] (const auto &cfg) { return std::make_unique(cfg); } } }, + { + "SL2B", + { "sl2b", [] (const auto &cfg) { return std::make_unique(cfg); } } + }, { "pwmx", { "pwmx", [] (const auto &cfg) { return std::make_unique(cfg); } } diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index bbaf2f05e7..75ce3d03c0 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -28,7 +28,7 @@ public: // Will blow up in a runtime exception if the file cannot be created. explicit Zipper(const std::string& zipfname, - e_compression level = FAST_COMPRESSION); + e_compression level = TIGHT_COMPRESSION); ~Zipper(); // No copies allwed, this is a file resource... diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 76f321d893..d02e46bc6b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -468,7 +468,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = { /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".sl2"sv, ".sl2b"sv, ".pwmx"sv } }, }; #if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR