[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 <pf@me>
pull/2889/head
pfeatherstone 11 months ago committed by GitHub
parent 3624bf9f05
commit 106c5a265d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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<std::size_t(char*,std::size_t)>;
callback_t* clb = static_cast<callback_t*>(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<std::size_t(char*,std::size_t)> clb)
{
read_image( std::unique_ptr<FileInfo>( 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<char>(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<FileInfo> 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<FileInfo>( 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<PngBufferReaderState*>( 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<FileInfo> 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_

@ -4,38 +4,41 @@
#define DLIB_PNG_IMPORT
#include <memory>
#include <cstring>
#include <functional>
#include <istream>
#include <fstream>
#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<typename T>
void get_image( T& t_) const
template<class image_type>
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<image_type>;
auto t = make_image_view(img);
typedef typename image_traits<T>::pixel_type pixel_type;
image_view<T> 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<pixel_type>::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<pixel_type>::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<uint8>(v[m*3]);
p.green = static_cast<uint8>(v[m*3+1]);
p.blue = static_cast<uint8>(v[m*3+2]);
p.red = static_cast<uint8>(lines[n][m*3]);
p.green = static_cast<uint8>(lines[n][m*3+1]);
p.blue = static_cast<uint8>(lines[n][m*3+2]);
assign_pixel( t[n][m], p );
}
}
}
else if (is_rgba() && bit_depth_ == 8)
{
if (!pixel_traits<pixel_type>::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<pixel_type>::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<uint8>(v[m*4]);
p.green = static_cast<uint8>(v[m*4+1]);
p.blue = static_cast<uint8>(v[m*4+2]);
p.alpha = static_cast<uint8>(v[m*4+3]);
p.red = static_cast<uint8>(lines[n][m*4]);
p.green = static_cast<uint8>(lines[n][m*4+1]);
p.blue = static_cast<uint8>(lines[n][m*4+2]);
p.alpha = static_cast<uint8>(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<FileInfo> check_file( const char* filename );
void read_image( std::unique_ptr<FileInfo> file_info );
unsigned height_, width_;
unsigned bit_depth_;
int color_type_;
std::unique_ptr<LibpngData> ld_;
std::unique_ptr<PngBufferReaderState> buffer_reader_state_;
void load(std::function<std::size_t(char*,std::size_t)> 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<void> finalizer;
};
// ----------------------------------------------------------------------------------------
template <
typename image_type
>
template <class image_type>
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 <class image_type>
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<is_byte<Byte>::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<const unsigned char*>(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

@ -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

@ -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<void(const char*, std::size_t)>;
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<void(const char*, std::size_t)> clb,
std::vector<unsigned char*>& 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);
}
}
}

@ -3,11 +3,13 @@
#ifndef DLIB_SAVE_PnG_Hh_
#define DLIB_SAVE_PnG_Hh_
#include <vector>
#include <string>
#include <fstream>
#include <functional>
#include "save_png_abstract.h"
#include "image_saver.h"
#include "../array2d.h"
#include <vector>
#include <string>
#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<void(const char*, std::size_t)> clb,
std::vector<unsigned char*>& 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<is_matrix<image_type> >::type save_png(
const image_type& img_,
const std::string& file_name
)
{
const_image_view<image_type> img(img_);
void save_png(
const image_type& img_,
std::function<void(const char*, std::size_t)> clb
)
{
const_image_view<image_type> 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<unsigned char*> row_pointers(img.nr());
typedef typename image_traits<image_type>::pixel_type pixel_type;
if (is_same_type<rgb_pixel,pixel_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<rgb_alpha_pixel,pixel_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<pixel_type>::lab || pixel_traits<pixel_type>::hsi || pixel_traits<pixel_type>::rgb)
{
// convert from Lab or HSI to RGB (Or potentially RGB pixels that aren't laid out as R G B)
array2d<rgb_pixel> 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<unsigned char*> row_pointers(img.nr());
using pixel_type = pixel_type_t<image_type>;
impl::impl_save_png(file_name, row_pointers, img.nc(), impl::png_type_rgb, 8);
}
else if (pixel_traits<pixel_type>::rgb_alpha)
{
// convert from RGBA pixels that aren't laid out as R G B A
array2d<rgb_alpha_pixel> 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<pixel_type>::grayscale, "impossible condition detected");
if (std::is_same<rgb_pixel,pixel_type>::value)
{
for (unsigned long i = 0; i < row_pointers.size(); ++i)
row_pointers[i] = (unsigned char*)(&img[i][0]);
if (pixel_traits<pixel_type>::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<bgr_pixel,pixel_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_gray, 8);
impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb, 8, true);
}
else if (pixel_traits<pixel_type>::is_unsigned && sizeof(pixel_type) == 2)
else if (std::is_same<rgb_alpha_pixel,pixel_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_gray, 16);
impl::impl_save_png(std::move(clb), row_pointers, img.nc(), impl::png_type_rgb_alpha, 8);
}
else
else if (pixel_traits<pixel_type>::lab || pixel_traits<pixel_type>::hsi || pixel_traits<pixel_type>::rgb)
{
// convert from whatever this is to 16bit grayscale
array2d<dlib::uint16> temp_img;
// convert from Lab or HSI to RGB (Or potentially RGB pixels that aren't laid out as R G B)
array2d<rgb_pixel> 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<pixel_type>::rgb_alpha)
{
// convert from RGBA pixels that aren't laid out as R G B A
array2d<rgb_alpha_pixel> 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<pixel_type>::grayscale, "impossible condition detected");
if (pixel_traits<pixel_type>::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<pixel_type>::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<dlib::uint16> 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<Byte, Alloc>& buf
)
{
static_assert(is_byte<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<T,NR,NC,MM,L>& img,
Output&& out
)
{
save_png(make_image_view(img), std::forward<Output>(out));
}
// ----------------------------------------------------------------------------------------
template <
class EXP,
class Output
>
void save_png (
const matrix_exp<EXP>& img,
const std::string& file_name
Output&& out
)
{
array2d<typename EXP::type> temp;
assign_image(temp, img);
save_png(temp, file_name);
save_png(temp, std::forward<Output>(out));
}
// ----------------------------------------------------------------------------------------

@ -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<Byte, Alloc>& 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
!*/
// ----------------------------------------------------------------------------------------
}

@ -243,7 +243,7 @@ namespace
img[r][c].alpha = static_cast<unsigned char>(r*14 + c + 4);
}
}
save_png(img, "test.png");
img.clear();
@ -275,9 +275,123 @@ namespace
}
}
}
#endif // DLIB_PNG_SUPPORT
{
matrix<rgb_alpha_pixel> img;
matrix<rgb_pixel> 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<unsigned char>(r*14 + c + 1);
img(r,c).green = static_cast<unsigned char>(r*14 + c + 2);
img(r,c).blue = static_cast<unsigned char>(r*14 + c + 3);
img(r,c).alpha = static_cast<unsigned char>(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<decltype(img1)>;
const std::string file_name = "test.png";
std::ostringstream out;
std::vector<char> buf1;
std::vector<int8_t> buf2;
std::vector<uint8_t> 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<rgb_pixel> img1(14,15);
array2d<bgr_pixel> img2(14,15);
for (long r = 0; r < 14; ++r)
{
for (long c = 0; c < 15; ++c)
{
img1[r][c].red = static_cast<unsigned char>(r*14 + c + 1);
img1[r][c].green = static_cast<unsigned char>(r*14 + c + 2);
img1[r][c].blue = static_cast<unsigned char>(r*14 + c + 3);
}
}
assign_image(img2, img1);
test_savers(img1);
test_savers(img2);
}
#endif // DLIB_PNG_SUPPORT
{
array2d<rgb_pixel> img;

Loading…
Cancel
Save