Просмотр исходного кода

Added functions to export raw image data to PNG for debugging purposes.
Renamed PNGRead.cpp/hpp to PNGReadWrite.cpp,hpp
EdgeGrid: Resurrected debugging output to PNG.

Vojtech Bubnik 4 лет назад
Родитель
Сommit
aa6ddfec85

+ 2 - 2
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

+ 37 - 56
src/libslic3r/EdgeGrid.cpp

@@ -3,16 +3,16 @@
 #include <float.h>
 #include <unordered_map>
 
-#if 0
-// #ifdef SLIC3R_GUI
-#include <wx/image.h>
-#endif /* SLIC3R_GUI */
+#include <png.h>
 
 #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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons)

+ 1 - 3
src/libslic3r/EdgeGrid.hpp

@@ -309,10 +309,8 @@ protected:
 	std::vector<float>							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
 

+ 1 - 1
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 <boost/property_tree/ini_parser.hpp>
 #include <boost/filesystem/path.hpp>

+ 0 - 100
src/libslic3r/PNGRead.cpp

@@ -1,100 +0,0 @@
-#include "PNGRead.hpp"
-
-#include <memory>
-
-#include <cstdio>
-#include <png.h>
-
-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<const std::uint8_t *>(rb.buf);
-    std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf);
-#else
-    auto buf = static_cast<png_const_bytep>(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<IStream *>(png_get_io_ptr(png_ptr));
-
-    if (!reader || !reader->is_ok()) return;
-
-    reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead);
-}
-
-bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
-{
-    static const constexpr int PNG_SIG_BYTES = 8;
-
-    std::vector<png_byte> 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<void *>(&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<png_bytep>(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

+ 219 - 0
src/libslic3r/PNGReadWrite.cpp

@@ -0,0 +1,219 @@
+#include "PNGReadWrite.hpp"
+
+#include <memory>
+
+#include <cstdio>
+#include <png.h>
+
+#include <boost/log/trivial.hpp>
+#include <boost/nowide/cstdio.hpp>
+
+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<const std::uint8_t *>(rb.buf);
+    std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf);
+#else
+    auto buf = static_cast<png_const_bytep>(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<IStream *>(png_get_io_ptr(png_ptr));
+
+    if (!reader || !reader->is_ok()) return;
+
+    reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead);
+}
+
+bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
+{
+    static const constexpr int PNG_SIG_BYTES = 8;
+
+    std::vector<png_byte> 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<void *>(&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<png_bytep>(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_byte**>(::png_malloc(png_ptr, height * sizeof(png_byte*)));
+    for (size_t y = 0; y < height; ++ y) {
+        auto row = reinterpret_cast<png_byte*>(::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<uint8_t> &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<uint8_t> 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<uint8_t> &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

+ 12 - 0
src/libslic3r/PNGRead.hpp → src/libslic3r/PNGReadWrite.hpp

@@ -65,6 +65,18 @@ template<class Img> 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<uint8_t> &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<uint8_t> &data_rgb, size_t scale);
+
 }}     // namespace Slic3r::png
 
 #endif // PNGREAD_HPP

+ 1 - 1
tests/libslic3r/test_png_io.cpp

@@ -3,7 +3,7 @@
 
 #include <numeric>
 
-#include "libslic3r/PNGRead.hpp"
+#include "libslic3r/PNGReadWrite.hpp"
 #include "libslic3r/SLA/AGGRaster.hpp"
 #include "libslic3r/BoundingBox.hpp"