1 #include "PNGReadWrite.hpp"
2 
3 #include <memory>
4 
5 #include <cstdio>
6 #include <png.h>
7 
8 #include <boost/log/trivial.hpp>
9 #include <boost/nowide/cstdio.hpp>
10 
11 namespace Slic3r { namespace png {
12 
13 struct PNGDescr {
14     png_struct *png = nullptr; png_info *info = nullptr;
15 
16     PNGDescr() = default;
17     PNGDescr(const PNGDescr&) = delete;
18     PNGDescr(PNGDescr&&) = delete;
19     PNGDescr& operator=(const PNGDescr&) = delete;
20     PNGDescr& operator=(PNGDescr&&) = delete;
21 
~PNGDescrSlic3r::png::PNGDescr22     ~PNGDescr()
23     {
24         if (png && info) png_destroy_info_struct(png, &info);
25         if (png) png_destroy_read_struct( &png, nullptr, nullptr);
26     }
27 };
28 
is_png(const ReadBuf & rb)29 bool is_png(const ReadBuf &rb)
30 {
31     static const constexpr int PNG_SIG_BYTES = 8;
32 
33 #if PNG_LIBPNG_VER_MINOR <= 2
34     // Earlier libpng versions had png_sig_cmp(png_bytep, ...) which is not
35     // a const pointer. It is not possible to cast away the const qualifier from
36     // the input buffer so... yes... life is challenging...
37     png_byte buf[PNG_SIG_BYTES];
38     auto inbuf = static_cast<const std::uint8_t *>(rb.buf);
39     std::copy(inbuf, inbuf + PNG_SIG_BYTES, buf);
40 #else
41     auto buf = static_cast<png_const_bytep>(rb.buf);
42 #endif
43 
44     return rb.sz >= PNG_SIG_BYTES && !png_sig_cmp(buf, 0, PNG_SIG_BYTES);
45 }
46 
47 // Buffer read callback for libpng. It provides an allocated output buffer and
48 // the amount of data it desires to read from the input.
png_read_callback(png_struct * png_ptr,png_bytep outBytes,png_size_t byteCountToRead)49 static void png_read_callback(png_struct *png_ptr,
50                               png_bytep   outBytes,
51                               png_size_t  byteCountToRead)
52 {
53     // Retrieve our input buffer through the png_ptr
54     auto reader = static_cast<IStream *>(png_get_io_ptr(png_ptr));
55 
56     if (!reader || !reader->is_ok()) return;
57 
58     reader->read(static_cast<std::uint8_t *>(outBytes), byteCountToRead);
59 }
60 
decode_png(IStream & in_buf,ImageGreyscale & out_img)61 bool decode_png(IStream &in_buf, ImageGreyscale &out_img)
62 {
63     static const constexpr int PNG_SIG_BYTES = 8;
64 
65     std::vector<png_byte> sig(PNG_SIG_BYTES, 0);
66     in_buf.read(sig.data(), PNG_SIG_BYTES);
67     if (!png_check_sig(sig.data(), PNG_SIG_BYTES))
68         return false;
69 
70     PNGDescr dsc;
71     dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
72                                      nullptr);
73 
74     if(!dsc.png) return false;
75 
76     dsc.info = png_create_info_struct(dsc.png);
77     if(!dsc.info) return false;
78 
79     png_set_read_fn(dsc.png, static_cast<void *>(&in_buf), png_read_callback);
80 
81     // Tell that we have already read the first bytes to check the signature
82     png_set_sig_bytes(dsc.png, PNG_SIG_BYTES);
83 
84     png_read_info(dsc.png, dsc.info);
85 
86     out_img.cols = png_get_image_width(dsc.png, dsc.info);
87     out_img.rows = png_get_image_height(dsc.png, dsc.info);
88     size_t color_type = png_get_color_type(dsc.png, dsc.info);
89     size_t bit_depth  = png_get_bit_depth(dsc.png, dsc.info);
90 
91     if (color_type != PNG_COLOR_TYPE_GRAY || bit_depth != 8)
92         return false;
93 
94     out_img.buf.resize(out_img.rows * out_img.cols);
95 
96     auto readbuf = static_cast<png_bytep>(out_img.buf.data());
97     for (size_t r = 0; r < out_img.rows; ++r)
98         png_read_row(dsc.png, readbuf + r * out_img.cols, nullptr);
99 
100     return true;
101 }
102 
103 // Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes.
104 // Based on https://www.lemoda.net/c/write-png/
write_rgb_to_file(const char * file_name_utf8,size_t width,size_t height,const uint8_t * data_rgb)105 bool write_rgb_to_file(const char *file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb)
106 {
107     bool         result       = false;
108 
109     // Forward declaration due to the gotos.
110     png_structp  png_ptr      = nullptr;
111     png_infop    info_ptr     = nullptr;
112     png_byte   **row_pointers = nullptr;
113 
114     FILE        *fp = boost::nowide::fopen(file_name_utf8, "wb");
115     if (! fp) {
116         BOOST_LOG_TRIVIAL(error) << "write_png_file: File could not be opened for writing: " << file_name_utf8;
117         goto fopen_failed;
118     }
119 
120     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
121     if (! png_ptr) {
122         BOOST_LOG_TRIVIAL(error) << "write_png_file: png_create_write_struct() failed";
123         goto png_create_write_struct_failed;
124     }
125 
126     info_ptr = png_create_info_struct(png_ptr);
127     if (! info_ptr) {
128         BOOST_LOG_TRIVIAL(error) << "write_png_file: png_create_info_struct() failed";
129         goto png_create_info_struct_failed;
130     }
131 
132     // Set up error handling.
133     if (setjmp(png_jmpbuf(png_ptr))) {
134         BOOST_LOG_TRIVIAL(error) << "write_png_file: setjmp() failed";
135         goto png_failure;
136     }
137 
138     // Set image attributes.
139     png_set_IHDR(png_ptr,
140         info_ptr,
141         png_uint_32(width),
142         png_uint_32(height),
143         8, // depth
144         PNG_COLOR_TYPE_RGB,
145         PNG_INTERLACE_NONE,
146         PNG_COMPRESSION_TYPE_DEFAULT,
147         PNG_FILTER_TYPE_DEFAULT);
148 
149     // Initialize rows of PNG.
150     row_pointers = reinterpret_cast<png_byte**>(::png_malloc(png_ptr, height * sizeof(png_byte*)));
151     for (size_t y = 0; y < height; ++ y) {
152         auto row = reinterpret_cast<png_byte*>(::png_malloc(png_ptr, sizeof(uint8_t) * width * 3));
153         row_pointers[y] = row;
154         memcpy(row, data_rgb + width * y * 3, sizeof(uint8_t) * width * 3);
155     }
156 
157     // Write the image data to "fp".
158     png_init_io(png_ptr, fp);
159     png_set_rows(png_ptr, info_ptr, row_pointers);
160     png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
161 
162     for (size_t y = 0; y < height; ++ y)
163         png_free(png_ptr, row_pointers[y]);
164     png_free(png_ptr, row_pointers);
165 
166     result = true;
167 
168 png_failure:
169 png_create_info_struct_failed:
170     ::png_destroy_write_struct(&png_ptr, &info_ptr);
171 png_create_write_struct_failed:
172     ::fclose(fp);
173 fopen_failed:
174     return result;
175 }
176 
write_rgb_to_file(const std::string & file_name_utf8,size_t width,size_t height,const uint8_t * data_rgb)177 bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const uint8_t *data_rgb)
178 {
179     return write_rgb_to_file(file_name_utf8.c_str(), width, height, data_rgb);
180 }
181 
write_rgb_to_file(const std::string & file_name_utf8,size_t width,size_t height,const std::vector<uint8_t> & data_rgb)182 bool write_rgb_to_file(const std::string &file_name_utf8, size_t width, size_t height, const std::vector<uint8_t> &data_rgb)
183 {
184     assert(width * height * 3 == data_rgb.size());
185     return write_rgb_to_file(file_name_utf8.c_str(), width, height, data_rgb.data());
186 }
187 
188 // Scaled variants are mostly useful for debugging purposes, for example to export images of low resolution distance fileds.
189 // Scaling is done by multiplying rows and columns without any smoothing to emphasise the original pixels.
write_rgb_to_file_scaled(const char * file_name_utf8,size_t width,size_t height,const uint8_t * data_rgb,size_t scale)190 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)
191 {
192     if (scale <= 1)
193         return write_rgb_to_file(file_name_utf8, width, height, data_rgb);
194     else {
195         std::vector<uint8_t> scaled(width * height * 3 * scale * scale);
196         uint8_t *dst = scaled.data();
197         for (size_t r = 0; r < height; ++ r) {
198             for (size_t repr = 0; repr < scale; ++ repr) {
199                 const uint8_t *row = data_rgb + width * 3 * r;
200                 for (size_t c = 0; c < width; ++ c) {
201                     for (size_t repc = 0; repc < scale; ++ repc) {
202                         *dst ++ = row[0];
203                         *dst ++ = row[1];
204                         *dst ++ = row[2];
205                     }
206                     row += 3;
207                 }
208             }
209         }
210         return write_rgb_to_file(file_name_utf8, width * scale, height * scale, scaled.data());
211     }
212 }
213 
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)214 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)
215 {
216     return write_rgb_to_file_scaled(file_name_utf8.c_str(), width, height, data_rgb, scale);
217 }
218 
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)219 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)
220 {
221     assert(width * height * 3 == data_rgb.size());
222     return write_rgb_to_file_scaled(file_name_utf8.c_str(), width, height, data_rgb.data(), scale);
223 }
224 
225 }} // namespace Slic3r::png
226