diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 290b8953c..aea324722 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -161,6 +161,8 @@ add_library(libslic3r STATIC PrintConfig.hpp PrintObject.cpp PrintRegion.cpp + PNGRead.hpp + PNGRead.cpp Semver.cpp ShortestPath.cpp ShortestPath.hpp @@ -308,6 +310,8 @@ target_link_libraries(libslic3r TBB::tbb libslic3r_cgal ${CMAKE_DL_LIBS} + PNG::PNG + ZLIB::ZLIB ) if (TARGET OpenVDB::openvdb) diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp new file mode 100644 index 000000000..8bfa3cb95 --- /dev/null +++ b/src/libslic3r/PNGRead.cpp @@ -0,0 +1,59 @@ +#include "PNGRead.hpp" + +#include + +#include +#include + +namespace Slic3r { namespace png { + +struct png_deleter { void operator()(png_struct *p) { + png_destroy_read_struct( &p, nullptr, nullptr); } +}; + +using png_ptr_t = std::unique_ptr; + +bool is_png(const ReadBuf &rb) +{ + static const constexpr int PNG_SIG_BYTES = 8; + + return rb.sz >= PNG_SIG_BYTES && + !png_sig_cmp(static_cast(rb.buf), 0, PNG_SIG_BYTES); +} + +bool decode_png(const ReadBuf &rb, ImageGreyscale &img) +{ + if (!is_png(rb)) return false; + + png_ptr_t png{png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr)}; + + if(!png) return false; + + png_infop info = png_create_info_struct(png.get()); + if(!info) return {}; + + FILE *io = ::fmemopen(const_cast(rb.buf), rb.sz, "rb"); + png_init_io(png.get(), io); + + png_read_info(png.get(), info); + + img.cols = png_get_image_width(png.get(), info); + img.rows = png_get_image_height(png.get(), info); + size_t color_type = png_get_color_type(png.get(), info); + size_t bit_depth = png_get_bit_depth(png.get(), info); + + if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8) + return false; + + img.buf.resize(img.rows * img.cols); + + auto readbuf = static_cast(img.buf.data()); + for (size_t r = 0; r < img.rows; ++r) + png_read_row(png.get(), readbuf + r * img.cols, nullptr); + + fclose(io); + + return true; +} + +}} diff --git a/src/libslic3r/PNGRead.hpp b/src/libslic3r/PNGRead.hpp new file mode 100644 index 000000000..88a948395 --- /dev/null +++ b/src/libslic3r/PNGRead.hpp @@ -0,0 +1,30 @@ +#ifndef PNGREAD_HPP +#define PNGREAD_HPP + +#include +#include + +namespace Slic3r { namespace png { + +struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; }; + +template struct Image { + std::vector buf; + size_t rows, cols; + PxT get(size_t row, size_t col) const { return buf[row * cols + col]; } +}; + +struct RGB { uint8_t r, g, b; }; + +using ImageRGB = Image; +using ImageGreyscale = Image; + +// TODO +// bool decode_png(Buffer &&pngbuf, ImageRGB &img); + +bool is_png(const ReadBuf &pngbuf); + +bool decode_png(const ReadBuf &pngbuf, ImageGreyscale &img); + +}} +#endif // PNGREAD_HPP diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp index 65ec46343..13ea60339 100644 --- a/src/slic3r/Utils/SLAImport.cpp +++ b/src/slic3r/Utils/SLAImport.cpp @@ -9,32 +9,31 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLA/RasterBase.hpp" #include "libslic3r/miniz_extension.hpp" +#include "libslic3r/PNGRead.hpp" #include #include #include -#include #include namespace marchsq { -// Specialize this struct to register a raster type for the Marching squares alg -template<> struct _RasterTraits { - using Rst = wxImage; - +template<> struct _RasterTraits { + using Rst = Slic3r::png::ImageGreyscale; + // The type of pixel cell in the raster using ValueType = uint8_t; - + // Value at a given position static uint8_t get(const Rst &rst, size_t row, size_t col) { - return rst.GetRed(col, row); + return rst.get(row, col); } // Number of rows and cols of the raster - static size_t rows(const Rst &rst) { return rst.GetHeight(); } - static size_t cols(const Rst &rst) { return rst.GetWidth(); } + static size_t rows(const Rst &rst) { return rst.rows; } + static size_t cols(const Rst &rst) { return rst.cols; } }; } // namespace marchsq @@ -44,7 +43,6 @@ namespace Slic3r { namespace { struct PNGBuffer { std::vector buf; std::string fname; }; - struct ArchiveData { boost::property_tree::ptree profile, config; std::vector images; @@ -69,8 +67,8 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, } PNGBuffer read_png(const mz_zip_archive_file_stat &entry, - MZ_Archive & zip, - const std::string & name) + MZ_Archive & zip, + const std::string & name) { std::vector buf(entry.m_uncomp_size); @@ -259,9 +257,13 @@ std::vector extract_slices_from_sla_archive( } } - PNGBuffer &png = arch.images[i]; - wxMemoryInputStream stream{png.buf.data(), png.buf.size()}; - wxImage img{stream}; +// PNGBuffer &png = arch.images[i]; +// wxMemoryInputStream stream{png.buf.data(), png.buf.size()}; +// wxImage img{stream}; + + png::ImageGreyscale img; + png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()}; + png::decode_png(rb, img); auto rings = marchsq::execute(img, 128, rstp.win); ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 5a1e8f18b..30b93eafc 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(${_TEST_NAME}_tests test_marchingsquares.cpp test_timeutils.cpp test_voronoi.cpp + test_png_io.cpp + test_timeutils.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp new file mode 100644 index 000000000..3378d0062 --- /dev/null +++ b/tests/libslic3r/test_png_io.cpp @@ -0,0 +1,52 @@ +#define NOMINMAX +#include + +#include "libslic3r/PNGRead.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" + +using namespace Slic3r; + +static sla::RasterGrayscaleAA create_raster(const sla::RasterBase::Resolution &res) +{ + sla::RasterBase::PixelDim pixdim{1., 1.}; + + auto bb = BoundingBox({0, 0}, {scaled(1.), scaled(1.)}); + sla::RasterBase::Trafo trafo; + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); + + return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)}; +} + +TEST_CASE("PNG read", "[PNG]") { + auto rst = create_raster({100, 100}); + + size_t rstsum = 0; + for (size_t r = 0; r < rst.resolution().height_px; ++r) + for (size_t c = 0; c < rst.resolution().width_px; ++c) + rstsum += rst.read_pixel(c, r); + + SECTION("Correct png buffer should be recognized as such.") { + auto enc_rst = rst.encode(sla::PNGRasterEncoder{}); + REQUIRE(Slic3r::png::is_png({enc_rst.data(), enc_rst.size()})); + } + + SECTION("Fake png buffer should be recognized as such.") { + std::vector fake(10, '\0'); + REQUIRE(!Slic3r::png::is_png({fake.data(), fake.size()})); + } + + SECTION("Decoded PNG buffer resolution should match the original") { + auto enc_rst = rst.encode(sla::PNGRasterEncoder{}); + + png::ImageGreyscale img; + png::decode_png({enc_rst.data(), enc_rst.size()}, img); + + REQUIRE(img.rows == rst.resolution().height_px); + REQUIRE(img.cols == rst.resolution().width_px); + + size_t sum = std::accumulate(img.buf.begin(), img.buf.end(), size_t(0)); + + REQUIRE(sum == rstsum); + } +}