diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 0cf5af93c..c475c6575 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -159,8 +159,8 @@ add_library(libslic3r STATIC PrintConfig.hpp PrintObject.cpp PrintRegion.cpp - PNGRead.hpp - PNGRead.cpp + PNGReadWrite.hpp + PNGReadWrite.cpp Semver.cpp ShortestPath.cpp ShortestPath.hpp diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index fa68092ee..e7307fda4 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -3,16 +3,16 @@ #include #include -#if 0 -// #ifdef SLIC3R_GUI -#include -#endif /* SLIC3R_GUI */ +#include #include "libslic3r.h" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" #include "SVG.hpp" +#include "PNGReadWrite.hpp" + +// #define EDGE_GRID_DEBUG_OUTPUT #if 0 // Enable debugging and assert in this file. @@ -677,6 +677,11 @@ struct PropagateDanielssonSingleVStep3 { void EdgeGrid::Grid::calculate_sdf() { +#ifdef EDGE_GRID_DEBUG_OUTPUT + static int iRun = 0; + ++ iRun; +#endif + // 1) Initialize a signum and an unsigned vector to a zero iso surface. size_t nrows = m_rows + 1; size_t ncols = m_cols + 1; @@ -774,19 +779,12 @@ void EdgeGrid::Grid::calculate_sdf() } } -#if 0 - static int iRun = 0; - ++ iRun; - if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) - wxImage::AddHandler(new wxPNGHandler); -//#ifdef SLIC3R_GUI +#ifdef EDGE_GRID_DEBUG_OUTPUT { - wxImage img(ncols, nrows); - unsigned char *data = img.GetData(); - memset(data, 0, ncols * nrows * 3); - for (coord_t r = 0; r < nrows; ++r) { - for (coord_t c = 0; c < ncols; ++c) { - unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + std::vector pixels(ncols * nrows * 3, 0); + for (coord_t r = 0; r < nrows; ++ r) { + for (coord_t c = 0; c < ncols; ++ c) { + uint8_t *pxl = pixels.data() + (((nrows - r - 1) * ncols) + c) * 3; float d = m_signed_distance_field[r * ncols + c]; if (d != search_radius) { float s = 255 * d / search_radius; @@ -802,15 +800,13 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("unsigned_df-%d.png", iRun), wxBITMAP_TYPE_PNG); + png::write_rgb_to_file_scaled(debug_out_path("unsigned_df-%d.png", iRun), ncols, nrows, pixels, 10); } { - wxImage img(ncols, nrows); - unsigned char *data = img.GetData(); - memset(data, 0, ncols * nrows * 3); - for (coord_t r = 0; r < nrows; ++r) { - for (coord_t c = 0; c < ncols; ++c) { - unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + std::vector pixels(ncols * nrows * 3, 0); + for (coord_t r = 0; r < nrows; ++ r) { + for (coord_t c = 0; c < ncols; ++ c) { + unsigned char *pxl = pixels.data() + (((nrows - r - 1) * ncols) + c) * 3; float d = m_signed_distance_field[r * ncols + c]; if (d != search_radius) { float s = 255 * d / search_radius; @@ -835,9 +831,9 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("signed_df-%d.png", iRun), wxBITMAP_TYPE_PNG); + png::write_rgb_to_file_scaled(debug_out_path("signed_df-%d.png", iRun), ncols, nrows, pixels, 10); } -#endif /* SLIC3R_GUI */ +#endif // EDGE_GRID_DEBUG_OUTPUT // 2) Propagate the signum. #define PROPAGATE_SIGNUM_SINGLE_STEP(DELTA) do { \ @@ -909,17 +905,14 @@ void EdgeGrid::Grid::calculate_sdf() } } -#if 0 -//#ifdef SLIC3R_GUI +#ifdef EDGE_GRID_DEBUG_OUTPUT { - wxImage img(ncols, nrows); - unsigned char *data = img.GetData(); - memset(data, 0, ncols * nrows * 3); + std::vector pixels(ncols * nrows * 3, 0); float search_radius = float(m_resolution * 5); for (coord_t r = 0; r < nrows; ++r) { for (coord_t c = 0; c < ncols; ++c) { - unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; - unsigned char sign = signs[r * ncols + c]; + uint8_t *pxl = pixels.data() + (((nrows - r - 1) * ncols) + c) * 3; + uint8_t sign = signs[r * ncols + c]; switch (sign) { case 0: // Positive, outside of a narrow band. @@ -960,20 +953,17 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("signed_df-signs-%d.png", iRun), wxBITMAP_TYPE_PNG); + png::write_rgb_to_file_scaled(debug_out_path("signed_df-signs-%d.png", iRun), ncols, nrows, pixels, 10); } -#endif /* SLIC3R_GUI */ +#endif // EDGE_GRID_DEBUG_OUTPUT -#if 0 -//#ifdef SLIC3R_GUI +#ifdef EDGE_GRID_DEBUG_OUTPUT { - wxImage img(ncols, nrows); - unsigned char *data = img.GetData(); - memset(data, 0, ncols * nrows * 3); + std::vector pixels(ncols * nrows * 3, 0); float search_radius = float(m_resolution * 5); for (coord_t r = 0; r < nrows; ++r) { for (coord_t c = 0; c < ncols; ++c) { - unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3; + uint8_t *pxl = pixels.data() + (((nrows - r - 1) * ncols) + c) * 3; float d = m_signed_distance_field[r * ncols + c]; float s = 255.f * fabs(d) / search_radius; int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); @@ -989,9 +979,9 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("signed_df2-%d.png", iRun), wxBITMAP_TYPE_PNG); + png::write_rgb_to_file_scaled(debug_out_path("signed_df2-%d.png", iRun), ncols, nrows, pixels, 10); } -#endif /* SLIC3R_GUI */ +#endif // EDGE_GRID_DEBUG_OUTPUT } float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const @@ -1491,26 +1481,18 @@ bool EdgeGrid::Grid::has_intersecting_edges() const return false; } -#if 0 -void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) +void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path, size_t scale) { - if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) - wxImage::AddHandler(new wxPNGHandler); - unsigned int w = (bbox.max(0) - bbox.min(0) + resolution - 1) / resolution; unsigned int h = (bbox.max(1) - bbox.min(1) + resolution - 1) / resolution; - wxImage img(w, h); - unsigned char *data = img.GetData(); - memset(data, 0, w * h * 3); - static int iRun = 0; - ++iRun; - + std::vector pixels(w * h * 3, 0); + const coord_t search_radius = grid.resolution() * 2; const coord_t display_blend_radius = grid.resolution() * 2; for (coord_t r = 0; r < h; ++r) { for (coord_t c = 0; c < w; ++ c) { - unsigned char *pxl = data + (((h - r - 1) * w) + c) * 3; + unsigned char *pxl = pixels.data() + (((h - r - 1) * w) + c) * 3; Point pt(c * resolution + bbox.min(0), r * resolution + bbox.min(1)); coordf_t min_dist; bool on_segment = true; @@ -1584,9 +1566,8 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo } } - img.SaveFile(path, wxBITMAP_TYPE_PNG); + png::write_rgb_to_file_scaled(path, w, h, pixels, scale); } -#endif /* SLIC3R_GUI */ // Find all pairs of intersectiong edges from the set of polygons. std::vector> intersecting_edges(const Polygons &polygons) diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index fd13ddc7f..e8a8d4374 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -309,10 +309,8 @@ protected: std::vector m_signed_distance_field; }; -#if 0 // Debugging utility. Save the signed distance field. -extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path); -#endif /* SLIC3R_GUI */ +extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path, size_t scale = 1); } // namespace EdgeGrid diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index c4a9f5864..f04855182 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -18,7 +18,7 @@ #include "libslic3r/PrintConfig.hpp" #include "libslic3r/SLA/RasterBase.hpp" #include "libslic3r/miniz_extension.hpp" -#include "libslic3r/PNGRead.hpp" +#include "libslic3r/PNGReadWrite.hpp" #include #include diff --git a/src/libslic3r/PNGRead.cpp b/src/libslic3r/PNGRead.cpp deleted file mode 100644 index e66143b84..000000000 --- a/src/libslic3r/PNGRead.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "PNGRead.hpp" - -#include - -#include -#include - -namespace Slic3r { namespace png { - -struct PNGDescr { - png_struct *png = nullptr; png_info *info = nullptr; - - PNGDescr() = default; - PNGDescr(const PNGDescr&) = delete; - PNGDescr(PNGDescr&&) = delete; - PNGDescr& operator=(const PNGDescr&) = delete; - PNGDescr& operator=(PNGDescr&&) = delete; - - ~PNGDescr() - { - if (png && info) png_destroy_info_struct(png, &info); - if (png) png_destroy_read_struct( &png, nullptr, nullptr); - } -}; - -bool is_png(const ReadBuf &rb) -{ - static const constexpr int PNG_SIG_BYTES = 8; - -#if PNG_LIBPNG_VER_MINOR <= 2 - // Earlier libpng versions had png_sig_cmp(png_bytep, ...) which is not - // a const pointer. It is not possible to cast away the const qualifier from - // the input buffer so... yes... life is challenging... - png_byte buf[PNG_SIG_BYTES]; - auto inbuf = static_cast(rb.buf); - std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf); -#else - auto buf = static_cast(rb.buf); -#endif - - return rb.sz >= PNG_SIG_BYTES && !png_sig_cmp(buf, 0, PNG_SIG_BYTES); -} - -// Buffer read callback for libpng. It provides an allocated output buffer and -// the amount of data it desires to read from the input. -void png_read_callback(png_struct *png_ptr, - png_bytep outBytes, - png_size_t byteCountToRead) -{ - // Retrieve our input buffer through the png_ptr - auto reader = static_cast(png_get_io_ptr(png_ptr)); - - if (!reader || !reader->is_ok()) return; - - reader->read(static_cast(outBytes), byteCountToRead); -} - -bool decode_png(IStream &in_buf, ImageGreyscale &out_img) -{ - static const constexpr int PNG_SIG_BYTES = 8; - - std::vector sig(PNG_SIG_BYTES, 0); - in_buf.read(sig.data(), PNG_SIG_BYTES); - if (!png_check_sig(sig.data(), PNG_SIG_BYTES)) - return false; - - PNGDescr dsc; - dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, - nullptr); - - if(!dsc.png) return false; - - dsc.info = png_create_info_struct(dsc.png); - if(!dsc.info) return false; - - png_set_read_fn(dsc.png, static_cast(&in_buf), png_read_callback); - - // Tell that we have already read the first bytes to check the signature - png_set_sig_bytes(dsc.png, PNG_SIG_BYTES); - - png_read_info(dsc.png, dsc.info); - - out_img.cols = png_get_image_width(dsc.png, dsc.info); - out_img.rows = png_get_image_height(dsc.png, dsc.info); - size_t color_type = png_get_color_type(dsc.png, dsc.info); - size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info); - - if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8) - return false; - - out_img.buf.resize(out_img.rows * out_img.cols); - - auto readbuf = static_cast(out_img.buf.data()); - for (size_t r = 0; r < out_img.rows; ++r) - png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr); - - return true; -} - -}} // namespace Slic3r::png diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp new file mode 100644 index 000000000..2b7f64f6d --- /dev/null +++ b/src/libslic3r/PNGReadWrite.cpp @@ -0,0 +1,219 @@ +#include "PNGReadWrite.hpp" + +#include + +#include +#include + +#include +#include + +namespace Slic3r { namespace png { + +struct PNGDescr { + png_struct *png = nullptr; png_info *info = nullptr; + + PNGDescr() = default; + PNGDescr(const PNGDescr&) = delete; + PNGDescr(PNGDescr&&) = delete; + PNGDescr& operator=(const PNGDescr&) = delete; + PNGDescr& operator=(PNGDescr&&) = delete; + + ~PNGDescr() + { + if (png && info) png_destroy_info_struct(png, &info); + if (png) png_destroy_read_struct( &png, nullptr, nullptr); + } +}; + +bool is_png(const ReadBuf &rb) +{ + static const constexpr int PNG_SIG_BYTES = 8; + +#if PNG_LIBPNG_VER_MINOR <= 2 + // Earlier libpng versions had png_sig_cmp(png_bytep, ...) which is not + // a const pointer. It is not possible to cast away the const qualifier from + // the input buffer so... yes... life is challenging... + png_byte buf[PNG_SIG_BYTES]; + auto inbuf = static_cast(rb.buf); + std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf); +#else + auto buf = static_cast(rb.buf); +#endif + + return rb.sz >= PNG_SIG_BYTES && !png_sig_cmp(buf, 0, PNG_SIG_BYTES); +} + +// Buffer read callback for libpng. It provides an allocated output buffer and +// the amount of data it desires to read from the input. +static void png_read_callback(png_struct *png_ptr, + png_bytep outBytes, + png_size_t byteCountToRead) +{ + // Retrieve our input buffer through the png_ptr + auto reader = static_cast(png_get_io_ptr(png_ptr)); + + if (!reader || !reader->is_ok()) return; + + reader->read(static_cast(outBytes), byteCountToRead); +} + +bool decode_png(IStream &in_buf, ImageGreyscale &out_img) +{ + static const constexpr int PNG_SIG_BYTES = 8; + + std::vector sig(PNG_SIG_BYTES, 0); + in_buf.read(sig.data(), PNG_SIG_BYTES); + if (!png_check_sig(sig.data(), PNG_SIG_BYTES)) + return false; + + PNGDescr dsc; + dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); + + if(!dsc.png) return false; + + dsc.info = png_create_info_struct(dsc.png); + if(!dsc.info) return false; + + png_set_read_fn(dsc.png, static_cast(&in_buf), png_read_callback); + + // Tell that we have already read the first bytes to check the signature + png_set_sig_bytes(dsc.png, PNG_SIG_BYTES); + + png_read_info(dsc.png, dsc.info); + + out_img.cols = png_get_image_width(dsc.png, dsc.info); + out_img.rows = png_get_image_height(dsc.png, dsc.info); + size_t color_type = png_get_color_type(dsc.png, dsc.info); + size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info); + + if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8) + return false; + + out_img.buf.resize(out_img.rows * out_img.cols); + + auto readbuf = static_cast(out_img.buf.data()); + for (size_t r = 0; r < out_img.rows; ++r) + png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr); + + return true; +} + +// Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. +// Based on https://www.lemoda.net/c/write-png/ +bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb) +{ + bool result = false; + + FILE *fp = boost::nowide::fopen(file_name_utf8, "wb"); + if (! fp) { + BOOST_LOG_TRIVIAL(error) << "write_png_file: File could not be opened for writing: " << file_name_utf8; + goto fopen_failed; + } + + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (! png_ptr) { + BOOST_LOG_TRIVIAL(error) << "write_png_file: png_create_write_struct() failed"; + goto png_create_write_struct_failed; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (! info_ptr) { + BOOST_LOG_TRIVIAL(error) << "write_png_file: png_create_info_struct() failed"; + goto png_create_info_struct_failed; + } + + // Set up error handling. + if (setjmp(png_jmpbuf(png_ptr))) { + BOOST_LOG_TRIVIAL(error) << "write_png_file: setjmp() failed"; + goto png_failure; + } + + // Set image attributes. + png_set_IHDR(png_ptr, + info_ptr, + png_uint_32(width), + png_uint_32(height), + 8, // depth + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + // Initialize rows of PNG. + auto row_pointers = reinterpret_cast(::png_malloc(png_ptr, height * sizeof(png_byte*))); + for (size_t y = 0; y < height; ++ y) { + auto row = reinterpret_cast(::png_malloc(png_ptr, sizeof(uint8_t) * width * 3)); + row_pointers[y] = row; + memcpy(row, data_rgb + width * y * 3, sizeof(uint8_t) * width * 3); + } + + // Write the image data to "fp". + png_init_io(png_ptr, fp); + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + + for (size_t y = 0; y < height; ++ y) + png_free(png_ptr, row_pointers[y]); + png_free(png_ptr, row_pointers); + result = true; + +png_failure: +png_create_info_struct_failed: + ::png_destroy_write_struct(&png_ptr, &info_ptr); +png_create_write_struct_failed: + ::fclose(fp); +fopen_failed: + return result; +} + +bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb) +{ + return write_rgb_to_file(file_name_utf8.c_str(), width, height, data_rgb); +} + +bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const std::vector &data_rgb) +{ + assert(width * height * 3 == data_rgb.size()); + return write_rgb_to_file(file_name_utf8.c_str(), width, height, data_rgb.data()); +} + +// Scaled variants are mostly useful for debugging purposes, for example to export images of low resolution distance fileds. +// Scaling is done by multiplying rows and columns without any smoothing to emphasise the original pixels. +bool write_rgb_to_file_scaled(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb, size_t scale) +{ + if (scale <= 1) + return write_rgb_to_file(file_name_utf8, width, height, data_rgb); + else { + std::vector scaled(width * height * 3 * scale * scale); + uint8_t *dst = scaled.data(); + for (size_t r = 0; r < height; ++ r) { + for (size_t repr = 0; repr < scale; ++ repr) { + const uint8_t *row = data_rgb + width * 3 * r; + for (size_t c = 0; c < width; ++ c) { + for (size_t repc = 0; repc < scale; ++ repc) { + *dst ++ = row[0]; + *dst ++ = row[1]; + *dst ++ = row[2]; + } + row += 3; + } + } + } + return write_rgb_to_file(file_name_utf8, width * scale, height * scale, scaled.data()); + } +} + +bool write_rgb_to_file_scaled(const std::string &file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb, size_t scale) +{ + return write_rgb_to_file_scaled(file_name_utf8.c_str(), width, height, data_rgb, scale); +} + +bool write_rgb_to_file_scaled(const std::string &file_name_utf8, size_t width, size_t height, const std::vector &data_rgb, size_t scale) +{ + assert(width * height * 3 == data_rgb.size()); + return write_rgb_to_file_scaled(file_name_utf8.c_str(), width, height, data_rgb.data(), scale); +} + +}} // namespace Slic3r::png diff --git a/src/libslic3r/PNGRead.hpp b/src/libslic3r/PNGReadWrite.hpp similarity index 65% rename from src/libslic3r/PNGRead.hpp rename to src/libslic3r/PNGReadWrite.hpp index 082edd569..6be8ed555 100644 --- a/src/libslic3r/PNGRead.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -65,6 +65,18 @@ template bool decode_png(const ReadBuf &in_buf, Img &out_img) // TODO: std::istream of FILE* could be similarly adapted in case its needed... + + +// Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. +bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb); +bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb); +bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const std::vector &data_rgb); +// Scaled variants are mostly useful for debugging purposes, for example to export images of low resolution distance fileds. +// Scaling is done by multiplying rows and columns without any smoothing to emphasise the original pixels. +bool write_rgb_to_file_scaled(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb, size_t scale); +bool write_rgb_to_file_scaled(const std::string &file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb, size_t scale); +bool write_rgb_to_file_scaled(const std::string &file_name_utf8, size_t width, size_t height, const std::vector &data_rgb, size_t scale); + }} // namespace Slic3r::png #endif // PNGREAD_HPP diff --git a/tests/libslic3r/test_png_io.cpp b/tests/libslic3r/test_png_io.cpp index 51f94be32..b4fcd6255 100644 --- a/tests/libslic3r/test_png_io.cpp +++ b/tests/libslic3r/test_png_io.cpp @@ -3,7 +3,7 @@ #include -#include "libslic3r/PNGRead.hpp" +#include "libslic3r/PNGReadWrite.hpp" #include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/BoundingBox.hpp"