From 106c5a265daf81e2a4072fc498ba929190eb4b23 Mon Sep 17 00:00:00 2001 From: pfeatherstone <45853521+pfeatherstone@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:54:34 +0100 Subject: [PATCH] [PNG] save_png() now works with std::vector and std::ostream (#2873) * save_png() works with buffers and streams * optimization for bgr_pixel * explicit overloads for matrix<> and matrix_exp<>. Added docs for new savers * refactored loading implementations details. In my view, easier to read, mirrors saving implementations details, and supports iostreams. We could make the callback API public, and we could support a ton of APIs... * test for iostreams * oops * - reduced code size by templating byte type - docs * spelling * i hope i haven't made a pig's breakfast out of this --------- Co-authored-by: pf --- dlib/image_loader/png_loader.cpp | 333 +++++++----------------- dlib/image_loader/png_loader.h | 253 +++++++----------- dlib/image_loader/png_loader_abstract.h | 38 ++- dlib/image_saver/save_png.cpp | 53 ++-- dlib/image_saver/save_png.h | 231 ++++++++++------ dlib/image_saver/save_png_abstract.h | 64 +++++ dlib/test/image.cpp | 118 ++++++++- 7 files changed, 577 insertions(+), 513 deletions(-) diff --git a/dlib/image_loader/png_loader.cpp b/dlib/image_loader/png_loader.cpp index b85120f10..0b5e18d38 100644 --- a/dlib/image_loader/png_loader.cpp +++ b/dlib/image_loader/png_loader.cpp @@ -21,287 +21,141 @@ namespace dlib // ---------------------------------------------------------------------------------------- - struct LibpngData - { - png_bytep* row_pointers_; - png_structp png_ptr_; - png_infop info_ptr_; - png_infop end_info_; - }; - - struct PngBufferReaderState - { - const unsigned char* buffer_; - size_t buffer_size_; - size_t current_pos_; - }; - - struct FileInfo - { - FileInfo( FILE *fp, const char* filename ) : fp_( fp ), filename_( filename ) - { - } - - FileInfo( const unsigned char* buffer, size_t buffer_size ) : buffer_( buffer ), buffer_size_( buffer_size ) - { - } - - // no copying this object. - FileInfo(const FileInfo&) = delete; - FileInfo& operator=(const FileInfo&) = delete; - - ~FileInfo() - { - if ( fp_ != nullptr ) fclose( fp_ ); - } - - FILE* fp_{nullptr}; - const char* filename_{nullptr}; - const unsigned char* buffer_{nullptr}; - size_t buffer_size_{0}; - }; - -// ---------------------------------------------------------------------------------------- - - png_loader:: - png_loader( const char* filename ) : height_( 0 ), width_( 0 ) + // Don't do anything when libpng calls us to tell us about an error. Just return to + // our own code and throw an exception (at the long jump target). + void png_loader_user_error_fn_silent(png_structp png_struct, png_const_charp ) { - read_image( check_file( filename ) ); + longjmp(png_jmpbuf(png_struct),1); } -// ---------------------------------------------------------------------------------------- - - png_loader:: - png_loader( const std::string& filename ) : height_( 0 ), width_( 0 ) + void png_loader_user_warning_fn_silent(png_structp , png_const_charp ) { - read_image( check_file( filename.c_str() ) ); } -// ---------------------------------------------------------------------------------------- - - png_loader:: - png_loader( const dlib::file& f ) : height_( 0 ), width_( 0 ) + void png_reader_callback(png_structp png, png_bytep data, png_size_t length) { - read_image( check_file( f.full_name().c_str() ) ); + using callback_t = std::function; + callback_t* clb = static_cast(png_get_io_ptr(png)); + const auto ret = (*clb)((char*)data, length); + if (ret != length) + png_error(png, "png_loader: read error in png_reader_callback"); } // ---------------------------------------------------------------------------------------- - png_loader:: - png_loader( const unsigned char* image_buffer, size_t buffer_size ) : height_( 0 ), width_( 0 ) + void png_loader::load(std::function clb) { - read_image( std::unique_ptr( new FileInfo( image_buffer, buffer_size ) ) ); - } + // Read header + png_byte sig[8]; + if (clb((char*)sig, 8) != 8) + throw image_load_error("png_loader: error reading file stream"); + if (png_sig_cmp(sig, 0, 8 ) != 0) + throw image_load_error("png_loader: format error"); -// ---------------------------------------------------------------------------------------- + // Create structs + png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, &png_loader_user_error_fn_silent, &png_loader_user_warning_fn_silent ); + if (png_ptr == NULL) + throw image_load_error("Error while reading PNG file : png_create_read_struct()"); - const unsigned char* png_loader::get_row( unsigned i ) const - { - return ld_->row_pointers_[i]; - } + png_infop info_ptr = png_create_info_struct( png_ptr ); + if ( info_ptr == NULL ) + { + png_destroy_read_struct(&png_ptr, ( png_infopp )NULL, ( png_infopp )NULL ); + throw image_load_error("Error while reading PNG file : png_create_info_struct()"); + } -// ---------------------------------------------------------------------------------------- + png_infop end_info = png_create_info_struct( png_ptr ); + if ( end_info == NULL ) + { + png_destroy_read_struct(&png_ptr, &info_ptr, ( png_infopp )NULL ); + throw image_load_error("Error while reading PNG file : png_create_info_struct()"); + } - png_loader::~png_loader() - { - if ( ld_ && ld_->row_pointers_ != NULL ) - png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) ); - } + if (setjmp(png_jmpbuf(png_ptr))) + { + // If you get here, then there was an error while parsing. + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + throw image_load_error("png_loader: parse error"); + } -// ---------------------------------------------------------------------------------------- + png_set_palette_to_rgb(png_ptr); + png_set_read_fn(png_ptr, &clb, png_reader_callback); + png_set_sig_bytes(png_ptr, 8); + // flags force one byte per channel output + byte_orderer bo; + int png_transforms = PNG_TRANSFORM_PACKING; + if (bo.host_is_little_endian()) + png_transforms |= PNG_TRANSFORM_SWAP_ENDIAN; + png_read_png(png_ptr, info_ptr, png_transforms, NULL); - bool png_loader::is_gray() const - { - return ( color_type_ == PNG_COLOR_TYPE_GRAY ); - } + // If you get here, you are no longer affected by C's crazy longjmp + finalizer = std::shared_ptr(new char, [=](char* ptr) mutable { + delete ptr; + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + }); -// ---------------------------------------------------------------------------------------- + color_type = png_get_color_type( png_ptr, info_ptr ); + height = png_get_image_height( png_ptr, info_ptr ); + width = png_get_image_width( png_ptr, info_ptr ); + bit_depth_ = png_get_bit_depth( png_ptr, info_ptr ); + rows = (unsigned char**)png_get_rows( png_ptr, info_ptr ); - bool png_loader::is_graya() const - { - return ( color_type_ == PNG_COLOR_TYPE_GRAY_ALPHA ); - } + if (!is_gray() && !is_graya() && !is_rgb() && !is_rgba()) + throw image_load_error("png_loader: unsupported color type"); -// ---------------------------------------------------------------------------------------- + if (bit_depth_ != 8 && bit_depth_ != 16) + throw image_load_error("png_loader: unsupported bit depth of " + std::to_string(bit_depth_)); - bool png_loader::is_rgb() const - { - return ( color_type_ == PNG_COLOR_TYPE_RGB ); + if (rows == NULL) + throw image_load_error("png_loader: parse error"); } // ---------------------------------------------------------------------------------------- - bool png_loader::is_rgba() const + void png_loader::load(std::istream& in) { - return ( color_type_ == PNG_COLOR_TYPE_RGB_ALPHA ); + load([&](char* data, std::size_t ndata) { + in.read(data, ndata); + return in.gcount(); + }); } // ---------------------------------------------------------------------------------------- - - std::unique_ptr png_loader::check_file( const char* filename ) - { - if ( filename == NULL ) - { - throw image_load_error("png_loader: invalid filename, it is NULL"); - } - FILE *fp = fopen( filename, "rb" ); - if ( !fp ) - { - throw image_load_error(std::string("png_loader: unable to open file ") + filename); - } - return std::unique_ptr( new FileInfo( fp, filename ) ); + png_loader::png_loader(const unsigned char* image_buffer, std::size_t buffer_size) + { + std::size_t counter{0}; + load([&](char* data, std::size_t ndata) { + ndata = std::min(ndata, buffer_size - counter); + std::memcpy(data, image_buffer + counter, ndata); + counter += ndata; + return ndata; + }); } // ---------------------------------------------------------------------------------------- - // Don't do anything when libpng calls us to tell us about an error. Just return to - // our own code and throw an exception (at the long jump target). - void png_loader_user_error_fn_silent(png_structp png_struct, png_const_charp ) - { - longjmp(png_jmpbuf(png_struct),1); - } - void png_loader_user_warning_fn_silent(png_structp , png_const_charp ) + png_loader::png_loader(std::istream& in) { + load(in); } - void png_buffer_reader(png_structp png_ptr, png_bytep data, size_t length) + png_loader::png_loader( const char* filename ) { - PngBufferReaderState* state = static_cast( png_get_io_ptr( png_ptr ) ); - if ( length > ( state->buffer_size_ - state->current_pos_ ) ) - { - png_error(png_ptr, "png_loader: read error in png_buffer_reader"); - } - memcpy( data, state->buffer_ + state->current_pos_, length ); - state->current_pos_ += length; + std::ifstream in(filename, std::ios::binary); + load(in); } - void png_loader::read_image( std::unique_ptr file_info ) - { - DLIB_CASSERT(file_info); - - ld_.reset(new LibpngData); - - constexpr png_size_t png_header_size = 8; - std::string load_error_info; - - if ( file_info->fp_ != NULL ) - { - png_byte sig[png_header_size]; - if (fread( sig, 1, png_header_size, file_info->fp_ ) != png_header_size) - { - throw image_load_error(std::string("png_loader: error reading file ") + file_info->filename_); - } - load_error_info = std::string(" in file ") + file_info->filename_; - if ( png_sig_cmp( sig, 0, png_header_size ) != 0 ) - { - throw image_load_error(std::string("png_loader: format error") + load_error_info); - } - } - else - { - if ( file_info->buffer_ == NULL ) - { - throw image_load_error(std::string("png_loader: invalid image buffer, it is NULL")); - } - if ( file_info->buffer_size_ == 0 ) - { - throw image_load_error(std::string("png_loader: invalid image buffer size, it is 0")); - } - if ( file_info->buffer_size_ < png_header_size || - png_sig_cmp( (png_bytep)file_info->buffer_, 0, png_header_size ) != 0 ) - { - throw image_load_error(std::string("png_loader: format error in image buffer")); - } - - buffer_reader_state_.reset(new PngBufferReaderState); - } - - ld_->png_ptr_ = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, &png_loader_user_error_fn_silent, &png_loader_user_warning_fn_silent ); - if ( ld_->png_ptr_ == NULL ) - { - std::ostringstream sout; - sout << "Error, unable to allocate png structure" << std::endl; - const char* runtime_version = png_get_header_ver(NULL); - if (runtime_version && std::strcmp(PNG_LIBPNG_VER_STRING, runtime_version) != 0) - { - sout << "This is happening because you compiled against one version of libpng, but then linked to another." << std::endl; - sout << "Compiled against libpng version: " << PNG_LIBPNG_VER_STRING << std::endl; - sout << "Linking to this version of libpng: " << runtime_version << std::endl; - } - throw image_load_error(sout.str()); - } - ld_->info_ptr_ = png_create_info_struct( ld_->png_ptr_ ); - if ( ld_->info_ptr_ == NULL ) - { - png_destroy_read_struct( &( ld_->png_ptr_ ), ( png_infopp )NULL, ( png_infopp )NULL ); - throw image_load_error(std::string("png_loader: unable to allocate png info structure") + load_error_info); - } - ld_->end_info_ = png_create_info_struct( ld_->png_ptr_ ); - if ( ld_->end_info_ == NULL ) - { - png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), ( png_infopp )NULL ); - throw image_load_error(std::string("png_loader: unable to allocate png info structure") + load_error_info); - } + png_loader::png_loader( const std::string& filename ) : png_loader(filename.c_str()) {} + png_loader::png_loader( const dlib::file& f ) : png_loader(f.full_name()) {} - if (setjmp(png_jmpbuf(ld_->png_ptr_))) - { - // If we get here, we had a problem writing the file - png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) ); - throw image_load_error(std::string("png_loader: parse error") + load_error_info); - } - - png_set_palette_to_rgb(ld_->png_ptr_); - - if ( file_info->fp_ != NULL ) - { - png_init_io( ld_->png_ptr_, file_info->fp_ ); - } - else - { - buffer_reader_state_->buffer_ = file_info->buffer_; - buffer_reader_state_->buffer_size_ = file_info->buffer_size_; - // skipping header - buffer_reader_state_->current_pos_ = png_header_size; - png_set_read_fn( ld_->png_ptr_, buffer_reader_state_.get(), png_buffer_reader ); - } - - png_set_sig_bytes( ld_->png_ptr_, png_header_size ); - // flags force one byte per channel output - byte_orderer bo; - int png_transforms = PNG_TRANSFORM_PACKING; - if (bo.host_is_little_endian()) - png_transforms |= PNG_TRANSFORM_SWAP_ENDIAN; - png_read_png( ld_->png_ptr_, ld_->info_ptr_, png_transforms, NULL ); - height_ = png_get_image_height( ld_->png_ptr_, ld_->info_ptr_ ); - width_ = png_get_image_width( ld_->png_ptr_, ld_->info_ptr_ ); - bit_depth_ = png_get_bit_depth( ld_->png_ptr_, ld_->info_ptr_ ); - color_type_ = png_get_color_type( ld_->png_ptr_, ld_-> info_ptr_ ); - - - if (color_type_ != PNG_COLOR_TYPE_GRAY && - color_type_ != PNG_COLOR_TYPE_RGB && - color_type_ != PNG_COLOR_TYPE_RGB_ALPHA && - color_type_ != PNG_COLOR_TYPE_GRAY_ALPHA) - { - png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) ); - throw image_load_error(std::string("png_loader: unsupported color type") + load_error_info); - } - - if (bit_depth_ != 8 && bit_depth_ != 16) - { - png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) ); - throw image_load_error("png_loader: unsupported bit depth of " + cast_to_string(bit_depth_) + load_error_info); - } - - ld_->row_pointers_ = png_get_rows( ld_->png_ptr_, ld_->info_ptr_ ); +// ---------------------------------------------------------------------------------------- - if ( ld_->row_pointers_ == NULL ) - { - png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) ); - throw image_load_error(std::string("png_loader: parse error") + load_error_info); - } - } + bool png_loader::is_gray() const { return color_type == PNG_COLOR_TYPE_GRAY; } + bool png_loader::is_graya() const { return color_type == PNG_COLOR_TYPE_GRAY_ALPHA; } + bool png_loader::is_rgb() const { return color_type == PNG_COLOR_TYPE_RGB; } + bool png_loader::is_rgba() const { return color_type == PNG_COLOR_TYPE_RGB_ALPHA; } + unsigned int png_loader::bit_depth () const {return bit_depth_;} // ---------------------------------------------------------------------------------------- @@ -309,5 +163,4 @@ namespace dlib #endif // DLIB_PNG_SUPPORT -#endif // DLIB_PNG_LOADER_CPp_ - +#endif // DLIB_PNG_LOADER_CPp_ \ No newline at end of file diff --git a/dlib/image_loader/png_loader.h b/dlib/image_loader/png_loader.h index 547f10674..a0a4c2620 100644 --- a/dlib/image_loader/png_loader.h +++ b/dlib/image_loader/png_loader.h @@ -4,38 +4,41 @@ #define DLIB_PNG_IMPORT #include +#include +#include +#include +#include #include "png_loader_abstract.h" #include "image_loader.h" #include "../pixel.h" -#include "../dir_nav.h" +#include "../type_traits.h" #include "../test_for_odr_violations.h" +#include "../dir_nav.h" namespace dlib { - struct LibpngData; - struct PngBufferReaderState; - struct FileInfo; +// ---------------------------------------------------------------------------------------- + class png_loader : noncopyable { public: + png_loader( std::istream& in ); png_loader( const char* filename ); png_loader( const std::string& filename ); png_loader( const dlib::file& f ); - png_loader( const unsigned char* image_buffer, size_t buffer_size ); - ~png_loader(); + png_loader( const unsigned char* image_buffer, std::size_t buffer_size ); - bool is_gray() const; - bool is_graya() const; - bool is_rgb() const; - bool is_rgba() const; + bool is_gray() const; + bool is_graya() const; + bool is_rgb() const; + bool is_rgba() const; + unsigned int bit_depth () const; - unsigned int bit_depth () const { return bit_depth_; } - - template - void get_image( T& t_) const + template + void get_image( image_type& img) const { #ifndef DLIB_PNG_SUPPORT /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -45,202 +48,143 @@ namespace dlib to link against the libpng library. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ COMPILE_TIME_ASSERT(sizeof(T) == 0); -#endif +#else + using pixel_type = pixel_type_t; + auto t = make_image_view(img); - typedef typename image_traits::pixel_type pixel_type; - image_view t(t_); - t.set_size( height_, width_ ); + t.set_size( height, width ); - - if (is_gray() && bit_depth_ == 8) + const auto assign_gray = [&](const auto** lines) { - for ( unsigned n = 0; n < height_;n++ ) - { - const unsigned char* v = get_row( n ); - for ( unsigned m = 0; m < width_;m++ ) - { - unsigned char p = v[m]; - assign_pixel( t[n][m], p ); - } - } - } - else if (is_gray() && bit_depth_ == 16) - { - for ( unsigned n = 0; n < height_;n++ ) - { - const uint16* v = (uint16*)get_row( n ); - for ( unsigned m = 0; m < width_;m++ ) - { - dlib::uint16 p = v[m]; - assign_pixel( t[n][m], p ); - } - } - } - else if (is_graya() && bit_depth_ == 8) - { - for ( unsigned n = 0; n < height_;n++ ) - { - const unsigned char* v = get_row( n ); - for ( unsigned m = 0; m < width_; m++ ) - { - unsigned char p = v[m*2]; - if (!pixel_traits::has_alpha) - { - assign_pixel( t[n][m], p ); - } - else - { - unsigned char pa = v[m*2+1]; - rgb_alpha_pixel pix; - assign_pixel(pix, p); - assign_pixel(pix.alpha, pa); - assign_pixel(t[n][m], pix); - } - } - } - } - else if (is_graya() && bit_depth_ == 16) + for ( int n = 0; n < height; ++n ) + for ( int m = 0; m < width; ++m ) + assign_pixel( t[n][m], lines[n][m]); + }; + + const auto assign_gray_alpha = [&](const auto** lines) { - for ( unsigned n = 0; n < height_;n++ ) + for ( int n = 0; n < height; ++n ) { - const uint16* v = (uint16*)get_row( n ); - for ( unsigned m = 0; m < width_; m++ ) + for ( int m = 0; m < width; ++m ) { - dlib::uint16 p = v[m*2]; if (!pixel_traits::has_alpha) { - assign_pixel( t[n][m], p ); + assign_pixel(t[n][m], lines[n][m*2]); } else { - dlib::uint16 pa = v[m*2+1]; rgb_alpha_pixel pix; - assign_pixel(pix, p); - assign_pixel(pix.alpha, pa); + assign_pixel(pix, lines[n][m*2]); + assign_pixel(pix.alpha, lines[n][m*2+1]); assign_pixel(t[n][m], pix); } } } - } - else if (is_rgb() && bit_depth_ == 8) - { - for ( unsigned n = 0; n < height_;n++ ) - { - const unsigned char* v = get_row( n ); - for ( unsigned m = 0; m < width_;m++ ) - { - rgb_pixel p; - p.red = v[m*3]; - p.green = v[m*3+1]; - p.blue = v[m*3+2]; - assign_pixel( t[n][m], p ); - } - } - } - else if (is_rgb() && bit_depth_ == 16) + }; + + const auto assign_rgb = [&](const auto** lines) { - for ( unsigned n = 0; n < height_;n++ ) + for ( int n = 0; n < height;++n ) { - const uint16* v = (uint16*)get_row( n ); - for ( unsigned m = 0; m < width_;m++ ) + for ( int m = 0; m < width;++m ) { rgb_pixel p; - p.red = static_cast(v[m*3]); - p.green = static_cast(v[m*3+1]); - p.blue = static_cast(v[m*3+2]); + p.red = static_cast(lines[n][m*3]); + p.green = static_cast(lines[n][m*3+1]); + p.blue = static_cast(lines[n][m*3+2]); assign_pixel( t[n][m], p ); } } - } - else if (is_rgba() && bit_depth_ == 8) - { - if (!pixel_traits::has_alpha) - assign_all_pixels(t,0); + }; - for ( unsigned n = 0; n < height_;n++ ) - { - const unsigned char* v = get_row( n ); - for ( unsigned m = 0; m < width_;m++ ) - { - rgb_alpha_pixel p; - p.red = v[m*4]; - p.green = v[m*4+1]; - p.blue = v[m*4+2]; - p.alpha = v[m*4+3]; - assign_pixel( t[n][m], p ); - } - } - } - else if (is_rgba() && bit_depth_ == 16) + const auto assign_rgba = [&](const auto** lines) { if (!pixel_traits::has_alpha) assign_all_pixels(t,0); - for ( unsigned n = 0; n < height_;n++ ) + for ( int n = 0; n < height; ++n ) { - const uint16* v = (uint16*)get_row( n ); - for ( unsigned m = 0; m < width_;m++ ) + for ( int m = 0; m < width; ++m ) { rgb_alpha_pixel p; - p.red = static_cast(v[m*4]); - p.green = static_cast(v[m*4+1]); - p.blue = static_cast(v[m*4+2]); - p.alpha = static_cast(v[m*4+3]); + p.red = static_cast(lines[n][m*4]); + p.green = static_cast(lines[n][m*4+1]); + p.blue = static_cast(lines[n][m*4+2]); + p.alpha = static_cast(lines[n][m*4+3]); assign_pixel( t[n][m], p ); } } - } + }; + + const auto assign = [&](const auto** lines) + { + if (is_gray()) + assign_gray(lines); + + else if (is_graya()) + assign_gray_alpha(lines); + + else if (is_rgb()) + assign_rgb(lines); + + else if (is_rgba()) + assign_rgba(lines); + }; + + if (bit_depth_ == 8) + assign((const uint8_t**)(rows)); + + else if (bit_depth_ == 16) + assign((const uint16_t**)(rows)); +#endif } private: - const unsigned char* get_row( unsigned i ) const; - std::unique_ptr check_file( const char* filename ); - void read_image( std::unique_ptr file_info ); - unsigned height_, width_; - unsigned bit_depth_; - int color_type_; - std::unique_ptr ld_; - std::unique_ptr buffer_reader_state_; + void load(std::function clb); + void load(std::istream& in); + + int height{0}; + int width{0}; + int bit_depth_{0}; + int color_type{0}; + unsigned char** rows{nullptr}; + std::shared_ptr finalizer; }; // ---------------------------------------------------------------------------------------- - template < - typename image_type - > + template void load_png ( - image_type& image, - const std::string& file_name + image_type& img, + std::istream& in ) { - png_loader(file_name).get_image(image); + png_loader(in).get_image(img); } - template < - typename image_type - > + template void load_png ( - image_type& image, - const unsigned char* image_buffer, - size_t buffer_size + image_type& img, + const std::string& file_name ) { - png_loader(image_buffer, buffer_size).get_image(image); + png_loader(file_name).get_image(img); } template < - typename image_type - > + class image_type, + class Byte, + std::enable_if_t::value, bool> = true + > void load_png ( - image_type& image, - const char* image_buffer, - size_t buffer_size + image_type& img, + const Byte* image_buffer, + std::size_t buffer_size ) { - png_loader(reinterpret_cast(image_buffer), buffer_size).get_image(image); + png_loader((const unsigned char*)image_buffer, buffer_size).get_image(img); } - // ---------------------------------------------------------------------------------------- } @@ -249,5 +193,4 @@ namespace dlib #include "png_loader.cpp" #endif -#endif // DLIB_PNG_IMPORT - +#endif // DLIB_PNG_IMPORT \ No newline at end of file diff --git a/dlib/image_loader/png_loader_abstract.h b/dlib/image_loader/png_loader_abstract.h index 0faa9338d..908c60a56 100644 --- a/dlib/image_loader/png_loader_abstract.h +++ b/dlib/image_loader/png_loader_abstract.h @@ -78,6 +78,18 @@ namespace dlib us from loading the given PNG buffer. !*/ + png_loader( + std::istream& in + ); + /*! + ensures + - loads the PNG file from the c++ IO stream into this object + throws + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given PNG buffer. + !*/ + ~png_loader( ); /*! @@ -166,42 +178,46 @@ namespace dlib - performs: png_loader(file_name).get_image(image); !*/ +// ---------------------------------------------------------------------------------------- + template < - typename image_type + typename image_type, + typename Byte > void load_png ( image_type& image, - const unsigned char* image_buffer, + const Byte* image_buffer, size_t buffer_size ); /*! requires - image_type == an image object that implements the interface defined in dlib/image_processing/generic_image.h + - Byte is either char, int8_t, uint8_t or std::byte ensures - performs: png_loader(image_buffer, buffer_size).get_image(image); !*/ + +// ---------------------------------------------------------------------------------------- template < - typename image_type - > + class image_type + > void load_png ( - image_type& image, - const char* image_buffer, - size_t buffer_size + image_type& img, + std::istream& in ); /*! requires - image_type == an image object that implements the interface defined in dlib/image_processing/generic_image.h + - in is an input stream containing a complete PNG encoded image ensures - - performs: png_loader((unsigned char*)image_buffer, buffer_size).get_image(image); + - Reads and ecodes the PNG file located in stream !*/ // ---------------------------------------------------------------------------------------- } -#endif // DLIB_PNG_IMPORT_ABSTRACT - - +#endif // DLIB_PNG_IMPORT_ABSTRACT \ No newline at end of file diff --git a/dlib/image_saver/save_png.cpp b/dlib/image_saver/save_png.cpp index 1c96b929c..553cb399f 100644 --- a/dlib/image_saver/save_png.cpp +++ b/dlib/image_saver/save_png.cpp @@ -23,26 +23,32 @@ namespace dlib { } + void png_writer_data_callback(png_structp png, png_bytep data, png_size_t length) + { + using clb_t = std::function; + clb_t* clb = (clb_t*)png_get_io_ptr(png); + (*clb)((const char*)data, length); + } + + void png_writer_flush_callback(png_structp png) + { + /*no-op*/ + } + namespace impl { void impl_save_png ( - const std::string& file_name, + std::function clb, std::vector& row_pointers, const long width, const png_type type, - const int bit_depth + const int bit_depth, + const bool swap_rgb ) { - - FILE *fp; png_structp png_ptr; png_infop info_ptr; - /* Open the file */ - fp = fopen(file_name.c_str(), "wb"); - if (fp == NULL) - throw image_save_error("Unable to open " + file_name + " for writing."); - /* Create and initialize the png_struct with the desired error handler * functions. If you want to use the default stderr and longjump method, * you can supply NULL for the last three parameters. We also check that @@ -52,18 +58,14 @@ namespace dlib png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, &png_reader_user_error_fn_silent, &png_reader_user_warning_fn_silent); if (png_ptr == NULL) - { - fclose(fp); - throw image_save_error("Error while writing PNG file " + file_name); - } + throw image_save_error("Error while writing PNG file : png_create_write_struct()"); /* Allocate/initialize the image information data. REQUIRED */ info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { - fclose(fp); png_destroy_write_struct(&png_ptr, NULL); - throw image_save_error("Error while writing PNG file " + file_name); + throw image_save_error("Error while writing PNG file : png_create_info_struct()"); } /* Set error handling. REQUIRED if you aren't supplying your own @@ -72,9 +74,8 @@ namespace dlib if (setjmp(png_jmpbuf(png_ptr))) { /* If we get here, we had a problem writing the file */ - fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); - throw image_save_error("Error while writing PNG file " + file_name); + throw image_save_error("Error while writing PNG file"); } int color_type = 0; @@ -85,34 +86,32 @@ namespace dlib case png_type_gray: color_type = PNG_COLOR_TYPE_GRAY; break; default: { - fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); throw image_save_error("Invalid color type"); } } - - /* Set up the output control if you are using standard C streams */ - png_init_io(png_ptr, fp); - + png_set_write_fn( + png_ptr, &clb, + png_writer_data_callback, + png_writer_flush_callback + ); int png_transforms = PNG_TRANSFORM_IDENTITY; byte_orderer bo; if (bo.host_is_little_endian()) png_transforms |= PNG_TRANSFORM_SWAP_ENDIAN; - + if (swap_rgb) + png_transforms |= PNG_TRANSFORM_BGR; + const long height = row_pointers.size(); - png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_rows(png_ptr, info_ptr, &row_pointers[0]); png_write_png(png_ptr, info_ptr, png_transforms, NULL); /* Clean up after the write, and free any memory allocated */ png_destroy_write_struct(&png_ptr, &info_ptr); - - /* Close the file */ - fclose(fp); } } } diff --git a/dlib/image_saver/save_png.h b/dlib/image_saver/save_png.h index cddf03ff6..4139cf5c6 100644 --- a/dlib/image_saver/save_png.h +++ b/dlib/image_saver/save_png.h @@ -3,11 +3,13 @@ #ifndef DLIB_SAVE_PnG_Hh_ #define DLIB_SAVE_PnG_Hh_ +#include +#include +#include +#include #include "save_png_abstract.h" #include "image_saver.h" #include "../array2d.h" -#include -#include #include "../pixel.h" #include "../matrix/matrix_exp.h" #include "../image_transforms/assign_image.h" @@ -27,127 +29,200 @@ namespace dlib }; void impl_save_png ( - const std::string& file_name, + std::function clb, std::vector& row_pointers, const long width, const png_type type, - const int bit_depth + const int bit_depth, + const bool swap_rgb = false ); - } // ---------------------------------------------------------------------------------------- - template < - typename image_type + template < + class image_type > - typename disable_if >::type save_png( - const image_type& img_, - const std::string& file_name - ) - { - const_image_view img(img_); + void save_png( + const image_type& img_, + std::function clb + ) + { + const_image_view img(img_); - // make sure requires clause is not broken - DLIB_CASSERT(img.size() != 0, - "\t save_png()" - << "\n\t You can't save an empty image as a PNG" - ); + // make sure requires clause is not broken + DLIB_CASSERT(img.size() != 0, + "\t save_png()" + << "\n\t You can't save an empty image as a PNG" + ); #ifndef DLIB_PNG_SUPPORT - /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - You are getting this error because you are trying to use save_png() - but you haven't defined DLIB_PNG_SUPPORT. You must do so to use - this function. You must also make sure you set your build environment - to link against the libpng library. - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ - COMPILE_TIME_ASSERT(sizeof(image_type) == 0); + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + You are getting this error because you are trying to use save_png() + but you haven't defined DLIB_PNG_SUPPORT. You must do so to use + this function. You must also make sure you set your build environment + to link against the libpng library. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + COMPILE_TIME_ASSERT(sizeof(image_type) == 0); #else - std::vector row_pointers(img.nr()); - typedef typename image_traits::pixel_type pixel_type; - - if (is_same_type::value) - { - for (unsigned long i = 0; i < row_pointers.size(); ++i) - row_pointers[i] = (unsigned char*)(&img[i][0]); - - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_rgb, 8); - } - else if (is_same_type::value) - { - for (unsigned long i = 0; i < row_pointers.size(); ++i) - row_pointers[i] = (unsigned char*)(&img[i][0]); - - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_rgb_alpha, 8); - } - else if (pixel_traits::lab || pixel_traits::hsi || pixel_traits::rgb) - { - // convert from Lab or HSI to RGB (Or potentially RGB pixels that aren't laid out as R G B) - array2d temp_img; - assign_image(temp_img, img_); - for (unsigned long i = 0; i < row_pointers.size(); ++i) - row_pointers[i] = (unsigned char*)(&temp_img[i][0]); + std::vector row_pointers(img.nr()); + using pixel_type = pixel_type_t; - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_rgb, 8); - } - else if (pixel_traits::rgb_alpha) - { - // convert from RGBA pixels that aren't laid out as R G B A - array2d temp_img; - assign_image(temp_img, img_); - for (unsigned long i = 0; i < row_pointers.size(); ++i) - row_pointers[i] = (unsigned char*)(&temp_img[i][0]); - - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_rgb_alpha, 8); - } - else // this is supposed to be grayscale - { - DLIB_CASSERT(pixel_traits::grayscale, "impossible condition detected"); + if (std::is_same::value) + { + for (unsigned long i = 0; i < row_pointers.size(); ++i) + row_pointers[i] = (unsigned char*)(&img[i][0]); - if (pixel_traits::is_unsigned && sizeof(pixel_type) == 1) + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb, 8); + } + else if (std::is_same::value) { for (unsigned long i = 0; i < row_pointers.size(); ++i) row_pointers[i] = (unsigned char*)(&img[i][0]); - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_gray, 8); + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb, 8, true); } - else if (pixel_traits::is_unsigned && sizeof(pixel_type) == 2) + else if (std::is_same::value) { for (unsigned long i = 0; i < row_pointers.size(); ++i) row_pointers[i] = (unsigned char*)(&img[i][0]); - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_gray, 16); + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb_alpha, 8); } - else + else if (pixel_traits::lab || pixel_traits::hsi || pixel_traits::rgb) { - // convert from whatever this is to 16bit grayscale - array2d temp_img; + // convert from Lab or HSI to RGB (Or potentially RGB pixels that aren't laid out as R G B) + array2d temp_img; assign_image(temp_img, img_); for (unsigned long i = 0; i < row_pointers.size(); ++i) row_pointers[i] = (unsigned char*)(&temp_img[i][0]); - impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_gray, 16); + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb, 8); + } + else if (pixel_traits::rgb_alpha) + { + // convert from RGBA pixels that aren't laid out as R G B A + array2d temp_img; + assign_image(temp_img, img_); + for (unsigned long i = 0; i < row_pointers.size(); ++i) + row_pointers[i] = (unsigned char*)(&temp_img[i][0]); + + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb_alpha, 8); + } + else // this is supposed to be grayscale + { + DLIB_CASSERT(pixel_traits::grayscale, "impossible condition detected"); + + if (pixel_traits::is_unsigned && sizeof(pixel_type) == 1) + { + for (unsigned long i = 0; i < row_pointers.size(); ++i) + row_pointers[i] = (unsigned char*)(&img[i][0]); + + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_gray, 8); + } + else if (pixel_traits::is_unsigned && sizeof(pixel_type) == 2) + { + for (unsigned long i = 0; i < row_pointers.size(); ++i) + row_pointers[i] = (unsigned char*)(&img[i][0]); + + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_gray, 16); + } + else + { + // convert from whatever this is to 16bit grayscale + array2d temp_img; + assign_image(temp_img, img_); + for (unsigned long i = 0; i < row_pointers.size(); ++i) + row_pointers[i] = (unsigned char*)(&temp_img[i][0]); + + impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_gray, 16); + } } +#endif } + } +// ---------------------------------------------------------------------------------------- -#endif + template < + class image_type + > + void save_png ( + const image_type& img, + std::ostream& out + ) + { + impl::save_png ( + img, + [&out](const char* data, std::size_t ndata) { + out.write(data, ndata); + } + ); + } + +// ---------------------------------------------------------------------------------------- + template < + class image_type + > + void save_png ( + const image_type& img, + const std::string& file_name + ) + { + std::ofstream out(file_name, std::ios::binary); + save_png(img, out); } // ---------------------------------------------------------------------------------------- template < - typename EXP - > - void save_png( + class image_type, + class Byte, + class Alloc + > + void save_png ( + const image_type& img, + std::vector& buf + ) + { + static_assert(is_byte::value, "Byte must be char, int8_t or uint8_t"); + impl::save_png ( + img, + [&buf](const char* data, std::size_t ndata) { + buf.insert(end(buf), data, data + ndata); + } + ); + } + +// ---------------------------------------------------------------------------------------- + + template < + class T, long NR, long NC, class MM, class L, + class Output + > + void save_png ( + const matrix& img, + Output&& out + ) + { + save_png(make_image_view(img), std::forward(out)); + } + +// ---------------------------------------------------------------------------------------- + + template < + class EXP, + class Output + > + void save_png ( const matrix_exp& img, - const std::string& file_name + Output&& out ) { array2d temp; assign_image(temp, img); - save_png(temp, file_name); + save_png(temp, std::forward(out)); } // ---------------------------------------------------------------------------------------- diff --git a/dlib/image_saver/save_png_abstract.h b/dlib/image_saver/save_png_abstract.h index ae495d1f2..19f506fdb 100644 --- a/dlib/image_saver/save_png_abstract.h +++ b/dlib/image_saver/save_png_abstract.h @@ -40,6 +40,70 @@ namespace dlib - std::bad_alloc !*/ +// ---------------------------------------------------------------------------------------- + + template < + class image_type + > + void save_png ( + const image_type& img, + std::ostream& out + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h or a matrix expression + - image.size() != 0 + ensures + - writes the image to the output stream in the PNG (Portable Network Graphics) + format. + - image[0][0] will be in the upper left corner of the image. + - image[image.nr()-1][image.nc()-1] will be in the lower right + corner of the image. + - This routine can save images containing any type of pixel. However, save_png() can + only natively store the following pixel types: rgb_pixel, rgb_alpha_pixel, uint8, + and uint16. All other pixel types will be converted into one of these types as + appropriate before being saved to disk. + throws + - image_save_error + This exception is thrown if there is an error that prevents us from saving + the image. + - std::bad_alloc + !*/ + +// ---------------------------------------------------------------------------------------- + + template < + class image_type, + class Byte, + class Alloc + > + void save_png ( + const image_type& img, + std::vector& buf + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h or a matrix expression + - image.size() != 0 + ensures + - writes the image to the vector buffer in the PNG (Portable Network Graphics) + format. + - image[0][0] will be in the upper left corner of the image. + - image[image.nr()-1][image.nc()-1] will be in the lower right + corner of the image. + - This routine can save images containing any type of pixel. However, save_png() can + only natively store the following pixel types: rgb_pixel, rgb_alpha_pixel, uint8, + and uint16. All other pixel types will be converted into one of these types as + appropriate before being saved to disk. + throws + - image_save_error + This exception is thrown if there is an error that prevents us from saving + the image. + - std::bad_alloc + !*/ + // ---------------------------------------------------------------------------------------- } diff --git a/dlib/test/image.cpp b/dlib/test/image.cpp index 2c46595ae..674bddcec 100644 --- a/dlib/test/image.cpp +++ b/dlib/test/image.cpp @@ -243,7 +243,7 @@ namespace img[r][c].alpha = static_cast(r*14 + c + 4); } } - + save_png(img, "test.png"); img.clear(); @@ -275,9 +275,123 @@ namespace } } } -#endif // DLIB_PNG_SUPPORT + { + matrix img; + matrix img2, img3; + img.set_size(14,15); + img2.set_size(img.nr(),img.nc()); + img3.set_size(img.nr(),img.nc()); + for (long r = 0; r < 14; ++r) + { + for (long c = 0; c < 15; ++c) + { + img(r,c).red = static_cast(r*14 + c + 1); + img(r,c).green = static_cast(r*14 + c + 2); + img(r,c).blue = static_cast(r*14 + c + 3); + img(r,c).alpha = static_cast(r*14 + c + 4); + } + } + + save_png(img, "test.png"); + + img.set_size(0,0); + DLIB_TEST(img.nr() == 0); + DLIB_TEST(img.nc() == 0); + + load_png(img, "test.png"); + + DLIB_TEST(img.nr() == 14); + DLIB_TEST(img.nc() == 15); + + assign_all_pixels(img2, 255); + assign_all_pixels(img3, 0); + load_png(img2, "test.png"); + assign_image(img3, img); + + for (long r = 0; r < 14; ++r) + { + for (long c = 0; c < 15; ++c) + { + DLIB_TEST(img(r,c).red == r*14 + c + 1); + DLIB_TEST(img(r,c).green == r*14 + c + 2); + DLIB_TEST(img(r,c).blue == r*14 + c + 3); + DLIB_TEST(img(r,c).alpha == r*14 + c + 4); + + DLIB_TEST(img2(r,c).red == img3(r,c).red); + DLIB_TEST(img2(r,c).green == img3(r,c).green); + DLIB_TEST(img2(r,c).blue == img3(r,c).blue); + } + } + } + { + const auto test_savers = [](const auto& img1) + { + const auto test_pixels = [](const auto& img1, const auto& img2) + { + DLIB_TEST(img1.nr() == img2.nr()); + DLIB_TEST(img1.nc() == img2.nc()); + + for (long r = 0; r < img1.nr(); ++r) + { + for (long c = 0; c < img1.nc(); ++c) + { + DLIB_TEST(img1[r][c].red == r*14 + c + 1); + DLIB_TEST(img1[r][c].green == r*14 + c + 2); + DLIB_TEST(img1[r][c].blue == r*14 + c + 3); + DLIB_TEST(img1[r][c].red == img2[r][c].red); + DLIB_TEST(img1[r][c].green == img2[r][c].green); + DLIB_TEST(img1[r][c].blue == img2[r][c].blue); + } + } + }; + + using image_type = std::decay_t; + + const std::string file_name = "test.png"; + std::ostringstream out; + std::vector buf1; + std::vector buf2; + std::vector buf3; + + save_png(img1, file_name); + save_png(img1, out); + save_png(img1, buf1); + save_png(img1, buf2); + save_png(img1, buf3); + std::istringstream in(out.str()); + + image_type img2, img3, img4, img5, img6; + load_png(img2, file_name); + load_png(img3, in); + load_png(img4, (const char*)buf1.data(), buf1.size()); + load_png(img5, (const char*)buf1.data(), buf1.size()); + load_png(img6, (const char*)buf1.data(), buf1.size()); + test_pixels(img1, img2); + test_pixels(img1, img3); + test_pixels(img1, img4); + test_pixels(img1, img5); + test_pixels(img1, img6); + }; + + array2d img1(14,15); + array2d img2(14,15); + for (long r = 0; r < 14; ++r) + { + for (long c = 0; c < 15; ++c) + { + img1[r][c].red = static_cast(r*14 + c + 1); + img1[r][c].green = static_cast(r*14 + c + 2); + img1[r][c].blue = static_cast(r*14 + c + 3); + } + } + + assign_image(img2, img1); + test_savers(img1); + test_savers(img2); + } +#endif // DLIB_PNG_SUPPORT { array2d img;