diff --git a/.github/workflows/build_cpp.yml b/.github/workflows/build_cpp.yml index 451cb2455..5d0acf149 100644 --- a/.github/workflows/build_cpp.yml +++ b/.github/workflows/build_cpp.yml @@ -1,4 +1,4 @@ -name: C++ +name: C++ on: push: @@ -18,10 +18,14 @@ defaults: working-directory: dlib/test jobs: - ubuntu-latest-gcc-default: + ubuntu-latest-gcc-default: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v2 + - name: Install dependecies + run: | + sudo apt update + sudo apt install libwebp-dev - name: Configure run: cmake ${{ github.workspace }}/dlib/test -B ${{ env.build_dir }} - name: Build just tests @@ -36,9 +40,12 @@ jobs: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v2 - - name: Install gcc 11 + - name: Install dependecies run: | sudo apt update + sudo apt install libwebp-dev + - name: Install gcc 11 + run: | sudo apt install gcc-11 g++-11 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 --slave /usr/bin/g++ g++ /usr/bin/g++-11 --slave /usr/bin/gcov gcov /usr/bin/gcov-11 - name: Configure @@ -54,6 +61,10 @@ jobs: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v2 + - name: Install dependecies + run: | + sudo apt update + sudo apt install libwebp-dev - name: Configure run: | export CC=/usr/bin/clang @@ -70,6 +81,10 @@ jobs: runs-on: 'ubuntu-latest' steps: - uses: actions/checkout@v2 + - name: Install dependecies + run: | + sudo apt update + sudo apt install libwebp-dev - name: Install clang 13 run: | wget https://apt.llvm.org/llvm.sh diff --git a/dlib/CMakeLists.txt b/dlib/CMakeLists.txt index eb699df04..f9a188469 100644 --- a/dlib/CMakeLists.txt +++ b/dlib/CMakeLists.txt @@ -184,6 +184,8 @@ if (NOT TARGET dlib) "Disable this if you don't want to link against libgif" ) set (DLIB_JPEG_SUPPORT_STR "Disable this if you don't want to link against libjpeg" ) + set (DLIB_WEBP_SUPPORT_STR + "Disable this if you don't want to link against libwebp" ) set (DLIB_LINK_WITH_SQLITE3_STR "Disable this if you don't want to link against sqlite3" ) #set (DLIB_USE_FFTW_STR "Disable this if you don't want to link against fftw" ) @@ -240,10 +242,12 @@ if (NOT TARGET dlib) option(DLIB_USE_CUDA ${DLIB_USE_CUDA_STR} OFF) option(DLIB_PNG_SUPPORT ${DLIB_PNG_SUPPORT_STR} OFF) option(DLIB_GIF_SUPPORT ${DLIB_GIF_SUPPORT_STR} OFF) + option(DLIB_WEBP_SUPPORT ${DLIB_WEBP_SUPPORT_STR} OFF) #option(DLIB_USE_FFTW ${DLIB_USE_FFTW_STR} OFF) option(DLIB_USE_MKL_FFT ${DLIB_USE_MKL_FFT_STR} OFF) else() option(DLIB_JPEG_SUPPORT ${DLIB_JPEG_SUPPORT_STR} ON) + option(DLIB_WEBP_SUPPORT ${DLIB_WEBP_SUPPORT_STR} ON) option(DLIB_LINK_WITH_SQLITE3 ${DLIB_LINK_WITH_SQLITE3_STR} ON) option(DLIB_USE_BLAS ${DLIB_USE_BLAS_STR} ON) option(DLIB_USE_LAPACK ${DLIB_USE_LAPACK_STR} ON) @@ -255,6 +259,7 @@ if (NOT TARGET dlib) option(DLIB_USE_MKL_FFT ${DLIB_USE_MKL_FFT_STR} ON) endif() toggle_preprocessor_switch(DLIB_JPEG_SUPPORT) + toggle_preprocessor_switch(DLIB_WEBP_SUPPORT) toggle_preprocessor_switch(DLIB_USE_BLAS) toggle_preprocessor_switch(DLIB_USE_LAPACK) toggle_preprocessor_switch(DLIB_USE_CUDA) @@ -555,6 +560,20 @@ if (NOT TARGET dlib) image_saver/save_jpeg.cpp ) endif() + if (DLIB_WEBP_SUPPORT) + include(cmake_utils/find_libwebp.cmake) + if (WEBP_FOUND) + include_directories(${WEBP_INCLUDE_DIR}) + set (dlib_needed_libraries ${dlib_needed_libraries} ${WEBP_LIBRARY}) + set(source_files ${source_files} + image_loader/webp_loader.cpp + image_saver/save_webp.cpp + ) + else() + set(DLIB_WEBP_SUPPORT OFF CACHE BOOL ${DLIB_WEBP_SUPPORT_STR} FORCE ) + toggle_preprocessor_switch(DLIB_WEBP_SUPPORT) + endif() + endif() if (DLIB_USE_BLAS OR DLIB_USE_LAPACK OR DLIB_USE_MKL_FFT) diff --git a/dlib/cmake_utils/find_libwebp.cmake b/dlib/cmake_utils/find_libwebp.cmake new file mode 100644 index 000000000..b9e74c9f4 --- /dev/null +++ b/dlib/cmake_utils/find_libwebp.cmake @@ -0,0 +1,34 @@ +#============================================================================= +# Find WebP library +# From OpenCV +#============================================================================= +# Find the native WebP headers and libraries. +# +# WEBP_INCLUDE_DIRS - where to find webp/decode.h, etc. +# WEBP_LIBRARIES - List of libraries when using webp. +# WEBP_FOUND - True if webp is found. +#============================================================================= + +# Look for the header file. + +unset(WEBP_FOUND) + +FIND_PATH(WEBP_INCLUDE_DIR NAMES webp/decode.h) + +if(NOT WEBP_INCLUDE_DIR) + unset(WEBP_FOUND) +else() + MARK_AS_ADVANCED(WEBP_INCLUDE_DIR) + + # Look for the library. + FIND_LIBRARY(WEBP_LIBRARY NAMES webp) + MARK_AS_ADVANCED(WEBP_LIBRARY) + + # handle the QUIETLY and REQUIRED arguments and set WEBP_FOUND to TRUE if + # all listed variables are TRUE + INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(WebP DEFAULT_MSG WEBP_LIBRARY WEBP_INCLUDE_DIR) + + SET(WEBP_LIBRARIES ${WEBP_LIBRARY}) + SET(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR}) +endif() diff --git a/dlib/config.h.in b/dlib/config.h.in index 27dff2b27..8e46e7896 100644 --- a/dlib/config.h.in +++ b/dlib/config.h.in @@ -20,6 +20,7 @@ // You should also consider telling dlib to link against libjpeg, libpng, libgif, fftw, CUDA, // and a BLAS and LAPACK library. To do this you need to uncomment the following #defines. #cmakedefine DLIB_JPEG_SUPPORT +#cmakedefine DLIB_WEBP_SUPPORT #cmakedefine DLIB_PNG_SUPPORT #cmakedefine DLIB_GIF_SUPPORT #cmakedefine DLIB_USE_FFTW diff --git a/dlib/image_io.h b/dlib/image_io.h index 4fdc79881..9edf41db0 100644 --- a/dlib/image_io.h +++ b/dlib/image_io.h @@ -11,10 +11,12 @@ #include "image_loader/image_loader.h" #include "image_loader/png_loader.h" #include "image_loader/jpeg_loader.h" +#include "image_loader/webp_loader.h" #include "image_loader/load_image.h" #include "image_saver/image_saver.h" #include "image_saver/save_png.h" #include "image_saver/save_jpeg.h" +#include "image_saver/save_webp.h" #endif // DLIB_IMAGe_IO_ diff --git a/dlib/image_loader/load_image.h b/dlib/image_loader/load_image.h index e6ef48b7f..9e2c3b096 100644 --- a/dlib/image_loader/load_image.h +++ b/dlib/image_loader/load_image.h @@ -7,6 +7,7 @@ #include "../string.h" #include "png_loader.h" #include "jpeg_loader.h" +#include "webp_loader.h" #include "image_loader.h" #include #include @@ -25,6 +26,7 @@ namespace dlib PNG, DNG, GIF, + WEBP, UNKNOWN }; @@ -34,14 +36,15 @@ namespace dlib if (!file) throw image_load_error("Unable to open file: " + file_name); - char buffer[9]; - file.read((char*)buffer, 8); - buffer[8] = 0; + char buffer[13]; + file.read((char*)buffer, 12); + buffer[12] = 0; // Determine the true image type using link: // http://en.wikipedia.org/wiki/List_of_file_signatures + static const char *pngHeader = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"; - if (strcmp(buffer, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") == 0) + if (memcmp(buffer, pngHeader, strlen(pngHeader)) == 0) return PNG; else if(buffer[0]=='\xff' && buffer[1]=='\xd8' && buffer[2]=='\xff') return JPG; @@ -51,6 +54,9 @@ namespace dlib return DNG; else if(buffer[0]=='G' && buffer[1]=='I' && buffer[2] == 'F') return GIF; + else if(buffer[0]=='R' && buffer[1]=='I' && buffer[2] == 'F' && buffer[3] == 'F' && + buffer[8]=='W' && buffer[9]=='E' && buffer[10] == 'B' && buffer[11] == 'P') + return WEBP; return UNKNOWN; } @@ -82,6 +88,9 @@ namespace dlib #ifdef DLIB_JPEG_SUPPORT case image_file_type::JPG: load_jpeg(image, file_name); return; #endif +#ifdef DLIB_WEBP_SUPPORT + case image_file_type::WEBP: load_webp(image, file_name); return; +#endif #ifdef DLIB_GIF_SUPPORT case image_file_type::GIF: { diff --git a/dlib/image_loader/webp_loader.cpp b/dlib/image_loader/webp_loader.cpp new file mode 100644 index 000000000..2eb7d8911 --- /dev/null +++ b/dlib/image_loader/webp_loader.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2022 Davis E. King (davis@dlib.net), Martin Sandsmark, Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_WEBP_LOADER_CPp_ +#define DLIB_WEBP_LOADER_CPp_ + +// only do anything with this file if DLIB_WEBP_SUPPORT is defined +#ifdef DLIB_WEBP_SUPPORT + +#include "../array2d.h" +#include "../pixel.h" +#include "../dir_nav.h" +#include "webp_loader.h" + +#include +#include + +namespace dlib +{ + + static std::vector load_contents(const std::string& filename) + { + std::ifstream stream(filename, std::ios::binary); + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); + stream.seekg(0, std::ios_base::end); + std::vector buffer(stream.tellg()); + stream.seekg(0); + stream.read(reinterpret_cast(buffer.data()), buffer.size()); + return buffer; + } + +// ---------------------------------------------------------------------------------------- + + webp_loader:: + webp_loader(const char* filename) : height_(0), width_(0) + { + data_ = load_contents(filename); + get_info(); + } + +// ---------------------------------------------------------------------------------------- + + webp_loader:: + webp_loader(const std::string& filename) : height_(0), width_(0) + { + data_ = load_contents(filename); + get_info(); + } + +// ---------------------------------------------------------------------------------------- + + webp_loader:: + webp_loader(const dlib::file& f) : height_(0), width_(0) + { + data_ = load_contents(f.full_name()); + get_info(); + } + +// ---------------------------------------------------------------------------------------- + + webp_loader:: + webp_loader(const unsigned char* imgbuffer, size_t imgbuffersize) : height_(0), width_(0) + { + data_.resize(imgbuffersize); + memcpy(data_.data(), imgbuffer, imgbuffersize); + get_info(); + } + +// ---------------------------------------------------------------------------------------- + + void webp_loader::get_info() + { + if (!WebPGetInfo(data_.data(), data_.size(), &width_, &height_)) + { + throw image_load_error("webp_loader: Invalid header"); + } + } + +// ---------------------------------------------------------------------------------------- + + void webp_loader::read_argb(unsigned char *out, const size_t out_size, const int out_stride) const + { + if (!WebPDecodeARGBInto(data_.data(), data_.size(), out, out_size, out_stride)) + { + throw image_load_error("webp_loader: decoding failed"); + } + } + +// ---------------------------------------------------------------------------------------- + + void webp_loader::read_rgba(unsigned char *out, const size_t out_size, const int out_stride) const + { + if (!WebPDecodeRGBAInto(data_.data(), data_.size(), out, out_size, out_stride)) + { + throw image_load_error("webp_loader: decoding failed"); + } + } + +// ---------------------------------------------------------------------------------------- + + void webp_loader::read_bgra(unsigned char *out, const size_t out_size, const int out_stride) const + { + if (!WebPDecodeBGRAInto(data_.data(), data_.size(), out, out_size, out_stride)) + { + throw image_load_error("webp_loader: decoding failed"); + } + } + +// ---------------------------------------------------------------------------------------- + + void webp_loader::read_rgb(unsigned char *out, const size_t out_size, const int out_stride) const + { + if (!WebPDecodeRGBInto(data_.data(), data_.size(), out, out_size, out_stride)) + { + throw image_load_error("webp_loader: decoding failed"); + } + } + +// ---------------------------------------------------------------------------------------- + + void webp_loader::read_bgr(unsigned char *out, const size_t out_size, const int out_stride) const + { + if (!WebPDecodeBGRInto(data_.data(), data_.size(), out, out_size, out_stride)) + { + throw image_load_error("webp_loader: decoding failed"); + } + } + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_WEBP_SUPPORT + +#endif // DLIB_WEBP_LOADER_CPp_ + diff --git a/dlib/image_loader/webp_loader.h b/dlib/image_loader/webp_loader.h new file mode 100644 index 000000000..59561a676 --- /dev/null +++ b/dlib/image_loader/webp_loader.h @@ -0,0 +1,141 @@ +// Copyright (C) 2022 Davis E. King (davis@dlib.net), Martin Sandsmark, Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_WEBP_IMPORT +#define DLIB_WEBP_IMPORT + +#include + +#include "webp_loader_abstract.h" +#include "image_loader.h" +#include "../pixel.h" +#include "../dir_nav.h" +#include "../test_for_odr_violations.h" + +namespace dlib +{ + + class webp_loader : noncopyable + { + public: + + webp_loader(const char* filename); + webp_loader(const std::string& filename); + webp_loader(const dlib::file& f); + webp_loader(const unsigned char* imgbuffer, size_t buffersize); + + template + void get_image(image_type& image) const + { +#ifndef DLIB_WEBP_SUPPORT + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + You are getting this error because you are trying to use the webp_loader + object but you haven't defined DLIB_WEBP_SUPPORT. You must do so to use + this object. You must also make sure you set your build environment + to link against the libwebp library. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + static_assert(sizeof(image_type) == 0, "webp support not enabled."); +#endif + image_view vimg(image); + vimg.set_size(height_, width_); + typedef typename image_traits::pixel_type pixel_type; + + unsigned char* output = reinterpret_cast(image_data(vimg)); + const int stride = width_step(vimg); + const size_t output_size = stride * height_; + + if (pixel_traits::rgb_alpha) + { + if (pixel_traits::bgr_layout) + read_bgra(output, output_size, stride); + else + read_rgba(output, output_size, stride); + return; + } + if (pixel_traits::rgb) + { + if (pixel_traits::bgr_layout) + read_bgr(output, output_size, stride); + else + read_rgb(output, output_size, stride); + return; + } + // If we end up here, we are out of our fast path, and have to do it manually + + array2d decoded; + decoded.set_size(height_, width_); + unsigned char* output_dec = reinterpret_cast(image_data(decoded)); + const int stride_dec = width_step(decoded); + const size_t output_dec_size = stride_dec * height_; + + read_rgba(output_dec, output_dec_size, stride_dec); + + for (int r = 0; r < height_; r++) + { + for (int c = 0; c < width_; c++) + { + assign_pixel(vimg[r][c], decoded[r][c]); + } + } + } + + private: + void get_info(); + void read_bgra(unsigned char *out, const size_t out_size, const int out_stride) const; + void read_bgr(unsigned char *out, const size_t out_size, const int out_stride) const; + void read_rgba(unsigned char *out, const size_t out_size, const int out_stride) const; + void read_rgb(unsigned char *out, const size_t out_size, const int out_stride) const; + void read_argb(unsigned char *out, const size_t out_size, const int out_stride) const; + + int height_; + int width_; + std::vector data_; + }; + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + void load_webp ( + image_type& image, + const std::string& file_name + ) + { + webp_loader(file_name).get_image(image); + } + + template < + typename image_type + > + void load_webp ( + image_type& image, + const unsigned char* imgbuff, + size_t imgbuffsize + ) + { + webp_loader(imgbuff, imgbuffsize).get_image(image); + } + + template < + typename image_type + > + void load_webp ( + image_type& image, + const char* imgbuff, + size_t imgbuffsize + ) + { + webp_loader(reinterpret_cast(imgbuff), imgbuffsize).get_image(image); + } + +// ---------------------------------------------------------------------------------------- + +} + +#ifdef NO_MAKEFILE +#include "webp_loader.cpp" +#endif + +#endif // DLIB_WEBP_IMPORT + + diff --git a/dlib/image_loader/webp_loader_abstract.h b/dlib/image_loader/webp_loader_abstract.h new file mode 100644 index 000000000..ccca43259 --- /dev/null +++ b/dlib/image_loader/webp_loader_abstract.h @@ -0,0 +1,155 @@ +// Copyright (C) 2022 Davis E. King (davis@dlib.net), Martin Sandsmark, Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#undef DLIB_WEBP_IMPORT_ABSTRACT +#ifdef DLIB_WEBP_IMPORT_ABSTRACT + +#include "image_loader_abstract.h" +#include "../algs.h" +#include "../pixel.h" +#include "../dir_nav.h" +#include "../image_processing/generic_image.h" + +namespace dlib +{ + + class webp_loader : noncopyable + { + /*! + WHAT THIS OBJECT REPRESENTS + This object represents a class capable of loading WEBP image files. + Once an instance of it is created to contain a WEBP file from + disk you can obtain the image stored in it via get_image(). + !*/ + + public: + + webp_loader( + const char* filename + ); + /*! + ensures + - loads the WEBP file with the given file name into this object + throws + - std::bad_alloc + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given WEBP file. + !*/ + + webp_loader( + const std::string& filename + ); + /*! + ensures + - loads the WEBP file with the given file name into this object + throws + - std::bad_alloc + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given WEBP file. + !*/ + + webp_loader( + const dlib::file& f + ); + /*! + ensures + - loads the WEBP file with the given file name into this object + throws + - std::bad_alloc + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given WEBP file. + !*/ + + webp_loader( + const unsigned char* imgbuffer, + size_t buffersize + ); + /*! + ensures + - loads the WEBP from memory imgbuffer of size buffersize into this object + throws + - image_load_error + This exception is thrown if there is some error that prevents + us from loading the given WEBP buffer. + !*/ + + ~webp_loader( + ); + /*! + ensures + - all resources associated with *this has been released + !*/ + + template< + typename image_type + > + void get_image( + image_type& img + ) const; + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - loads the WEBP image stored in this object into img + !*/ + + }; + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + void load_webp ( + image_type& image, + const std::string& file_name + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - performs: webp_loader(file_name).get_image(image); + !*/ + + template < + typename image_type + > + void load_webp ( + image_type& image, + const unsigned char* imgbuff, + size_t imgbuffsize + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - performs: webp_loader(imgbuff, imgbuffsize).get_image(image); + !*/ + + template < + typename image_type + > + void load_webp ( + image_type& image, + const char* imgbuff, + size_t imgbuffsize + ); + /*! + requires + - image_type == an image object that implements the interface defined in + dlib/image_processing/generic_image.h + ensures + - performs: webp_loader((unsigned char*)imgbuff, imgbuffsize).get_image(image); + !*/ + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_WEBP_IMPORT_ABSTRACT + diff --git a/dlib/image_saver/save_webp.cpp b/dlib/image_saver/save_webp.cpp new file mode 100644 index 000000000..e1ee2118c --- /dev/null +++ b/dlib/image_saver/save_webp.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2022 Davis E. King (davis@dlib.net), Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_WEBP_SAVER_CPp_ +#define DLIB_WEBP_SAVER_CPp_ + +// only do anything with this file if DLIB_WEBP_SUPPORT is defined +#ifdef DLIB_WEBP_SUPPORT + +#include "save_webp.h" +#include "image_saver.h" +#include + +#include + +namespace dlib { + +// ---------------------------------------------------------------------------------------- + + namespace impl + { + void impl_save_webp ( + const std::string& filename, + const uint8_t* data, + const int width, + const int height, + const int stride, + const float quality, + const webp_type type + ) + { + if (width > WEBP_MAX_DIMENSION || height > WEBP_MAX_DIMENSION) + throw image_save_error("Error while encoding " + filename + ". Bad picture dimensions: " + + std::to_string(width) + "x" + std::to_string(height) + + ". Maximum WebP width and height allowed is " + + std::to_string(WEBP_MAX_DIMENSION) + " pixels"); + + std::ofstream fout(filename, std::ios::binary); + if (!fout.good()) + throw image_save_error("Unable to open " + filename + " for writing."); + + uint8_t* output; + size_t output_size = 0; + switch (type) + { + case webp_type::rgb: + if (quality > 100) + output_size = WebPEncodeLosslessRGB(data, width, height, stride, &output); + else + output_size = WebPEncodeRGB(data, width, height, stride, quality, &output); + break; + case webp_type::rgba: + if (quality > 100) + output_size = WebPEncodeLosslessRGBA(data, width, height, stride, &output); + else + output_size = WebPEncodeRGBA(data, width, height, stride, quality, &output); + break; + case webp_type::bgr: + if (quality > 100) + output_size = WebPEncodeLosslessBGR(data, width, height, stride, &output); + else + output_size = WebPEncodeBGR(data, width, height, stride, quality, &output); + break; + case webp_type::bgra: + if (quality > 100) + output_size = WebPEncodeLosslessBGRA(data, width, height, stride, &output); + else + output_size = WebPEncodeBGRA(data, width, height, stride, quality, &output); + break; + default: + throw image_save_error("Invalid WebP color type"); + } + + if (output_size > 0) + { + fout.write(reinterpret_cast(output), output_size); + if (!fout.good()) + throw image_save_error("Error while writing WebP image to " + filename + "."); + } + else + { + throw image_save_error("Error while encoding WebP image to " + filename + "."); + } + WebPFree(output); + } + } + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_WEBP_SUPPORT + +#endif // DLIB_WEBP_SAVER_CPp_ + diff --git a/dlib/image_saver/save_webp.h b/dlib/image_saver/save_webp.h new file mode 100644 index 000000000..fe736179a --- /dev/null +++ b/dlib/image_saver/save_webp.h @@ -0,0 +1,124 @@ +// Copyright (C) 2022 Davis E. King (davis@dlib.net), Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#ifndef DLIB_SAVE_WEBP_Hh_ +#define DLIB_SAVE_WEBP_Hh_ + +#include "save_webp_abstract.h" + +#include "../enable_if.h" +#include "image_saver.h" +#include "../matrix.h" +#include "../array2d.h" +#include "../pixel.h" +#include "../image_processing/generic_image.h" +#include + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + namespace impl + { + enum class webp_type + { + rgb, + bgr, + rgba, + bgra + }; + + void impl_save_webp ( + const std::string& filename, + const uint8_t* data, + const int width, + const int height, + const int stride, + const float quality, + const webp_type type + ); + } + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + typename disable_if>::type save_webp ( + const image_type& img_, + const std::string& filename, + float quality = 75 + ) + { +#ifndef DLIB_WEBP_SUPPORT + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + You are getting this error because you are trying to use the save_webp + function but you haven't defined DLIB_WEBP_SUPPORT. You must do so to use + this object. You must also make sure you set your build environment + to link against the libwebp library. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + static_assert(sizeof(image_type) == 0, "webp support not enabled."); +#endif + const_image_view img(img_); + using pixel_type = typename image_traits::pixel_type; + + // make sure requires clause is not broken + DLIB_CASSERT(img.size() != 0, + "\t save_webp()" + << "\n\t You can't save an empty image as a WEBP." + ); + DLIB_CASSERT(0 <= quality, + "\t save_webp()" + << "\n\t Invalid quality value." + << "\n\t quality: " << quality + ); + + auto data = reinterpret_cast(image_data(img)); + const int width = img.nc(); + const int height = img.nr(); + const int stride = width_step(img); + if (pixel_traits::rgb_alpha) + { + if (pixel_traits::bgr_layout) + impl::impl_save_webp(filename, data, width, height, stride, quality, impl::webp_type::bgra); + else + impl::impl_save_webp(filename, data, width, height, stride, quality, impl::webp_type::rgba); + } + else if (pixel_traits::rgb) + { + if (pixel_traits::bgr_layout) + impl::impl_save_webp(filename, data, width, height, stride, quality, impl::webp_type::bgr); + else + impl::impl_save_webp(filename, data, width, height, stride, quality, impl::webp_type::rgb); + } + else + { + // This is some other kind of color image so just save it as an RGB image. + array2d temp; + assign_image(temp, img); + auto data = reinterpret_cast(image_data(temp)); + impl::impl_save_webp(filename, data, width, height, stride, quality, impl::webp_type::rgb); + } + } + +// ---------------------------------------------------------------------------------------- + + template < + typename EXP + > + void save_webp( + const matrix_exp& img, + const std::string& filename, + float quality = 75 + ) + { + array2d temp; + assign_image(temp, img); + save_webp(temp, filename, quality); + } + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_WEBP_SUPPORT diff --git a/dlib/image_saver/save_webp_abstract.h b/dlib/image_saver/save_webp_abstract.h new file mode 100644 index 000000000..87a693085 --- /dev/null +++ b/dlib/image_saver/save_webp_abstract.h @@ -0,0 +1,54 @@ +// Copyright (C) 2022 Davis E. King (davis@dlib.net), Adrià Arrufat +// License: Boost Software License See LICENSE.txt for the full license. +#undef DLIB_SAVE_WEBP_ABSTRACT_Hh_ +#ifdef DLIB_SAVE_WEBP_ABSTRACT_Hh_ + +#include "../image_processing/generic_image.h" +#include "../pixel.h" +#include + +namespace dlib +{ + +// ---------------------------------------------------------------------------------------- + + template < + typename image_type + > + void save_webp ( + const image_type& img, + const std::string& filename, + float quality = 75 + ); + /*! + 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 + - quality >= 0 + ensures + - writes the image to the file indicated by filename in the WEBP 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_webp() can only natively store rgb_pixel, bgr_pixel, rgb_alpha_pixel and + bgr_alpha_pixel pixel types. All other pixel types will be converted into one + of these types as appropriate before being saved to disk. + - The quality value determines how lossy the compression is. Larger quality + values result in larger output images but the images will look better. A value + between 0 and 100 will use lossy compression, while any value larger than + 100 will perform lossless compression. + throws + - image_save_error + This exception is thrown if there is an error that prevents us from saving + the image. + - std::bad_alloc + !*/ + +// ---------------------------------------------------------------------------------------- + +} + +#endif // DLIB_SAVE_WEBP_ABSTRACT_Hh_ + diff --git a/dlib/pixel.h b/dlib/pixel.h index 42b50b2cd..6e471fa69 100644 --- a/dlib/pixel.h +++ b/dlib/pixel.h @@ -39,6 +39,7 @@ namespace dlib - bool lab - bool has_alpha + - bool bgr_layout - long num @@ -55,6 +56,7 @@ namespace dlib - This type of pixel represents the RGB color space. - num == 3 - has_alpha == false + - bgr_layout == true if the channel order is BGR, and false if it's RGB - basic_pixel_type == unsigned char - min() == 0 - max() == 255 @@ -68,6 +70,7 @@ namespace dlib with maximum opacity. - num == 4 - has_alpha == true + - bgr_layout == true if the channel order is BGR, and false if it's RGB - basic_pixel_type == unsigned char - min() == 0 - max() == 255 @@ -219,6 +222,46 @@ namespace dlib }; +// ---------------------------------------------------------------------------------------- + + struct bgr_alpha_pixel + { + /*! + WHAT THIS OBJECT REPRESENTS + This is a simple struct that represents an BGR colored graphical pixel + with an alpha channel. + !*/ + + bgr_alpha_pixel ( + ) {} + + bgr_alpha_pixel ( + unsigned char blue_, + unsigned char green_, + unsigned char red_, + unsigned char alpha_ + ) : blue(blue_), green(green_), red(red_), alpha(alpha_) {} + + unsigned char blue; + unsigned char green; + unsigned char red; + unsigned char alpha; + + bool operator == (const bgr_alpha_pixel& that) const + { + return this->blue == that.blue + && this->green == that.green + && this->red == that.red + && this->alpha == that.alpha; + } + + bool operator != (const bgr_alpha_pixel& that) const + { + return !(*this == that); + } + + }; + // ---------------------------------------------------------------------------------------- struct hsi_pixel @@ -474,6 +517,7 @@ namespace dlib struct pixel_traits { constexpr static bool rgb = true; + constexpr static bool bgr_layout = false; constexpr static bool rgb_alpha = false; constexpr static bool grayscale = false; constexpr static bool hsi = false; @@ -492,6 +536,7 @@ namespace dlib struct pixel_traits { constexpr static bool rgb = true; + constexpr static bool bgr_layout = true; constexpr static bool rgb_alpha = false; constexpr static bool grayscale = false; constexpr static bool hsi = false; @@ -510,6 +555,26 @@ namespace dlib struct pixel_traits { constexpr static bool rgb = false; + constexpr static bool bgr_layout = false; + constexpr static bool rgb_alpha = true; + constexpr static bool grayscale = false; + constexpr static bool hsi = false; + constexpr static bool lab = false; + constexpr static long num = 4; + typedef unsigned char basic_pixel_type; + static basic_pixel_type min() { return 0;} + static basic_pixel_type max() { return 255;} + constexpr static bool is_unsigned = true; + constexpr static bool has_alpha = true; + }; + +// ---------------------------------------------------------------------------------------- + + template <> + struct pixel_traits + { + constexpr static bool rgb = false; + constexpr static bool bgr_layout = true; constexpr static bool rgb_alpha = true; constexpr static bool grayscale = false; constexpr static bool hsi = false; @@ -529,6 +594,7 @@ namespace dlib struct pixel_traits { constexpr static bool rgb = false; + constexpr static bool bgr_layout = false; constexpr static bool rgb_alpha = false; constexpr static bool grayscale = false; constexpr static bool hsi = true; @@ -548,6 +614,7 @@ namespace dlib struct pixel_traits { constexpr static bool rgb = false; + constexpr static bool bgr_layout = false; constexpr static bool rgb_alpha = false; constexpr static bool grayscale = false; constexpr static bool hsi = false; @@ -566,6 +633,7 @@ namespace dlib struct grayscale_pixel_traits { constexpr static bool rgb = false; + constexpr static bool bgr_layout = false; constexpr static bool rgb_alpha = false; constexpr static bool grayscale = true; constexpr static bool hsi = false; diff --git a/dlib/test/image.cpp b/dlib/test/image.cpp index 5659b0388..442fe55aa 100644 --- a/dlib/test/image.cpp +++ b/dlib/test/image.cpp @@ -2306,6 +2306,119 @@ namespace DLIB_TEST(image == result); } + template + double psnr(const matrix& img1, const matrix& img2) + { + DLIB_TEST(have_same_dimensions(img1, img2)); + double mse = 0; + const long nk = width_step(img1) / img1.nc(); + const long data_size = img1.size() * nk; + const bool has_alpha = nk == 4; + auto data1 = reinterpret_cast(image_data(img1)); + auto data2 = reinterpret_cast(image_data(img2)); + for (long i = 0; i < data_size; i += nk) + { + // We are using the default WebP settings, which means 'exact' is disabled. + // RGB values in transparent areas will be modified to improve compression. + // As a result, we skip matching transparent pixels. + if (has_alpha && data1[i + 3] == 0 && data2[i + 3] == 0) + continue; + for (long k = 0; k < nk; ++k) + mse += std::pow(static_cast(data1[i + k]) - static_cast(data2[i + k]), 2); + } + mse /= data_size; + return 20 * std::log10(pixel_traits::max()) - 10 * std::log10(mse); + } + + void test_webp() + { +#ifdef DLIB_WEBP_SUPPORT + print_spinner(); + matrix rgb_img, rgb_dec; + matrix rgba_img, rgba_dec; + matrix bgr_img, bgr_dec; + matrix bgra_img, bgra_dec; + // this is a matrix of the 32x32 dlib logo + const std::string data{ + "gP79Jk3Zra0ocokRFuNsUUuZg4phoFDDKp9wTEPIHgX3GSQfaiwJIZGVecNTfibjcx8QxSZplz/p" + "st61fSu3N26BEzFl16L7kOsPiktkqJb7poGAXYngdkNPClBOWV0zV300nMmcpKoQ6e9IxLJ9ouJJ" + "++dwNeTNdQN6Ehhk7jdBM62XV1YzcehwRuApNugTjSozbTEqTSdrz1ftXP7rhgVLkWNrnZ2Cd+oF" + "qkIcZKWsnpz0K1JiFMz7J+d3S4aTQSWKH2qezLT/YKGfZsyT3pUCwwdYi/dF/EaUg7mhlRdk66wD" + "WYtoAFObeu85hQbU/uEVhwK6NBSUfLmwIQYtGw+Kr2qKaiTIS6wzcyIRUvI0sVSVeWBXNYPHq6z7" + "t6XcLG5ipOSvGDCUa5nXqnQ8tLKrRpewxvy6M8pzwmw3FqXt3oqq5umxi3MpU5PgU5dFuDor30G/" + "IJr+FtHDWBQHrHlZmbg/NNllK2d0D+fTPNfTYEmOaTdMO8av36523OJK8zXvKWqgPccgjchbIv8O" + "VfO3PwooK9XA23Bt4+nqMlQ9cs9FXmW/QyaoI5/996fShh/KFeup1dsF7VyLu5BDnGX+/t80SaAf" + "2MNPYJZs27klBwZs39u2Eyk37UKTZPK7YFQW+pbMhHInHswcV2TsQHMEA9RONwhkeR7O0JSJrryy" + "2gXvadc+oRqux0Zs1mpOn2f2BSfZnGDbKcgUwcDucI69J30NEKHlvnqZ+4tA3frIEuhZrub9IrHs" + "gQK7HCzJsWfKoF7/p/unykiAL5zh5chNToECkcNNHY9IkRn56bZ6iLv5TrQmWkknjAdP9fY50E1q" + "+asVnWv9DgrtXwJE0pZn/3tHA9CizwkfmF0zu/c0BBixXmi1Vh3E9pf3yVHQIfj96gLGBWTBkLQq" + "DoqWRialGKmH4hnLbNTBqc3lkisAAWdBsobZ5fB27waJbOnoI9LwR6TOohN9IYPR4cSIwStp8qhK" + "np+20vwmZNqPsoD9F9SOlfprkAmrHcP7rw/+yf5fMWps4qj3GuWAElkECI791I4aQsjMlL4aKPoJ" + "+ZAVIooYh1uQLQWNrgxAZRxydbgPAKUs+5pYgGT6Io+HH5piTiuSJowgkBUzA4fX2TmZOaCCLZql" + "d0UnsvoDQLnDhx+uIqzCDut4QDsMRH6j29i1+CnVv6Ye/AAD9GUNlSSIS5pX22gfd6YXrEQYayQH" + "mN72c8oHiw9zf6g/Xz0XCpkXi1ebiDlI21RnHuYw/ml9U0QWqUSk8kPAdUPL1QE7DB+/r1sf0A/+" + "IcJuiEXFvglVJpmfCewfvvtzEJHO1wLdK32mZ97CQvu9Er6zrc+bY5Gh9IOL+loqz8etxQE8I2bI" + "9+s0MTYZgHPZnqJK1NsFLHWGyUNzoRr63jkwQjdiJn1p/lbtzOa5TkhsOLzAR+jjb8Inueg32frv" + "xRHC2o92KkTqOomEmghgnGtdXl77vYTYMt/CUcbqGOAwiSQzw6jZtA5UTXI/6Fc14lMD3BBThbbm" + "KV9u+n5SN66r6hogiFiWUoU9gG20AaFCz4Lk2EqBRM+SnRaNoY/u1zIFRu/8JoghowDNB9hCQgqT" + "14AwF1TDGNTm1hgRtRX5lOrx8WgTXbwGl5YXHqN+oufn3m9FQ1dWxtqHH+dwNUD4MvEGIbNPauBA" + "+Fr4ozAJ8xBrovuVQjxfswoz/xkYPoRbWjhZuYi3vWqeCf6Pvp1+Ak3rd2cRV5HXAjksFE54eerP" + "Kst9eHlpTTEbe2pRs4V+nlZgPI0/lH0z5D8IRBriTX/kujcqwZAj5rDQQndhT4FYxEj7ldZuxC2D" + "yxenJ2goCV0x+UPjPxjRPCHb1EiIX7evPtyvr5UI2O48e7sixwA=" + }; + ostringstream sout; + istringstream sin; + base64 base64_coder; + compress_stream::kernel_1ea compressor; + sin.str(data); + base64_coder.decode(sin, sout); + sin.clear(); + sin.str(sout.str()); + sout.clear(); + sout.str(""); + compressor.decompress(sin, sout); + sin.clear(); + sin.str(sout.str()); + deserialize(rgba_img, sin); + for (auto quality : {75., 101.}) + { + save_webp(rgba_img, "test_rgba.webp", quality); + load_webp(rgba_dec, "test_rgba.webp"); + if (quality > 100) + DLIB_TEST(psnr(rgba_img, rgba_dec) == std::numeric_limits::infinity()); + else + DLIB_TEST(psnr(rgba_img, rgba_dec) > 30); + + assign_image(bgra_img, rgba_img); + save_webp(bgra_img, "test_bgra.webp", quality); + load_webp(bgra_dec, "test_bgra.webp"); + if (quality > 100) + DLIB_TEST(psnr(bgra_img, bgra_dec) == std::numeric_limits::infinity()); + else + DLIB_TEST(psnr(bgra_img, bgra_dec) > 30); + + // Here we assign an image with an alpha channel to an image without an alpha channel. + // Since we are not using the exact mode in WebP, the PSNR will be quite low, since + // pixels in transparent areas will have different values. + assign_image(rgb_img, rgba_img); + save_webp(rgb_img, "test_rgb.webp", quality); + load_webp(rgb_dec, "test_rgb.webp"); + if (quality > 100) + DLIB_TEST(psnr(rgb_img, rgb_dec) == std::numeric_limits::infinity()); + else + DLIB_TEST(psnr(rgb_img, rgb_dec) > 15); + + assign_image(bgr_img, rgb_img); + save_webp(bgr_img, "test_bgr.webp", quality); + load_webp(bgr_dec, "test_bgr.webp"); + if (quality > 100) + DLIB_TEST(psnr(bgr_img, bgr_dec) == std::numeric_limits::infinity()); + else + DLIB_TEST(psnr(bgr_img, bgr_dec) > 15); + } +#endif + } + // ---------------------------------------------------------------------------------------- class image_tester : public tester @@ -2411,6 +2524,7 @@ namespace test_interpolate_bilinear(); test_letterbox_image(); test_draw_string(); + test_webp(); } } a; diff --git a/docs/docs/imaging.xml b/docs/docs/imaging.xml index eca72b037..b6062118a 100644 --- a/docs/docs/imaging.xml +++ b/docs/docs/imaging.xml @@ -68,6 +68,7 @@ rgb_pixel bgr_pixel rgb_alpha_pixel + bgr_alpha_pixel hsi_pixel lab_pixel pixel_traits @@ -89,6 +90,9 @@ save_dng save_png save_jpeg + webp_loader + load_webp + save_webp
@@ -852,7 +856,27 @@ - + + + + + bgr_alpha_pixel + dlib/pixel.h + dlib/pixel.h + + This is a simple struct that represents an BGR colored graphical pixel with an + alpha channel. +

+ The difference between this object and the rgb_alpha_pixel + is just that this struct lays its pixels down in memory in BGR order rather + than RGB order. You only care about this if you are doing something like + using the cv_image object to map an OpenCV image + into a more object oriented form. +

+
+ +
+ @@ -1067,7 +1091,26 @@ - + + + + + webp_loader + dlib/image_io.h + dlib/image_loader/webp_loader_abstract.h + + This object loads a WebP image file into + an array2d of pixels. +

+ Note that you must define DLIB_WEBP_SUPPORT if you want to use this object. You + must also set your build environment to link to the libwebp library. However, + if you use CMake and dlib's default CMakeLists.txt file then it will get setup + automatically. +

+
+ +
+ @@ -1078,7 +1121,7 @@ This function loads a JPEG image file into an array2d of pixels.

- Note that you must define DLIB_JPEG_SUPPORT if you want to use this object. You + Note that you must define DLIB_JPEG_SUPPORT if you want to use this function. You must also set your build environment to link to the libjpeg library. However, if you use CMake and dlib's default CMakeLists.txt file then it will get setup automatically. @@ -1100,6 +1143,24 @@ + + + + load_webp + dlib/image_io.h + dlib/image_loader/image_loader_abstract.h + + This function loads a WebP image file into + an array2d of pixels. +

+ Note that you must define DLIB_WEBP_SUPPORT if you want to use this function. You + must also set your build environment to link to the libwebp library. However, + if you use CMake and dlib's default CMakeLists.txt file then it will get setup + automatically. +

+ +
+ @@ -1169,7 +1230,32 @@ - + + + + + save_webp + dlib/image_io.h + dlib/image_saver/save_webp_abstract.h + + This global function writes an image to disk as a WebP file. +

+ Note that you must define DLIB_WEBP_SUPPORT if you want to use this function. You + must also set your build environment to link to the libwebp library. However, + if you use CMake and dlib's default CMakeLists.txt file then it will get setup + automatically. +

+

+ This routine can save images containing any type of pixel. However, save_webp() can + only natively store the following pixel types: rgb_pixel, + rgb_alpha_pixel, bgr_pixel, and bgr_alpha_pixel. + All other pixel types will be converted into one of these types as appropriate + before being saved to disk. +

+
+ +
+ @@ -1219,7 +1305,7 @@ This function loads a Portable Network Graphics (PNG) image file into an array2d of pixels.

- Note that you must define DLIB_PNG_SUPPORT if you want to use this object. You + Note that you must define DLIB_PNG_SUPPORT if you want to use this function. You must also set your build environment to link to the libpng library. However, if you use CMake and dlib's default CMakeLists.txt file then it will get setup automatically. diff --git a/docs/docs/release_notes.xml b/docs/docs/release_notes.xml index bfee7761a..fbdc8e84c 100644 --- a/docs/docs/release_notes.xml +++ b/docs/docs/release_notes.xml @@ -14,6 +14,8 @@ New Features and Improvements: - Added Beta distribution to dlib::rand. - Added directory_exists. + - Added bgr_alpha_pixel type. + - Added support for loading and saving WebP images. - Deep learning tooling: - Added ReOrg layer. - Added visitor to draw network architectures using the DOT language. diff --git a/docs/docs/term_index.xml b/docs/docs/term_index.xml index 865892dcc..abaa0419c 100644 --- a/docs/docs/term_index.xml +++ b/docs/docs/term_index.xml @@ -1529,6 +1529,7 @@ + @@ -1536,12 +1537,14 @@ + + @@ -1569,6 +1572,7 @@ + diff --git a/examples/dnn_instance_segmentation_ex.cpp b/examples/dnn_instance_segmentation_ex.cpp index b864015b9..2aa296ea5 100644 --- a/examples/dnn_instance_segmentation_ex.cpp +++ b/examples/dnn_instance_segmentation_ex.cpp @@ -61,7 +61,7 @@ int main(int argc, char** argv) try // Find supported image files. const std::vector files = dlib::get_files_in_directory_tree(argv[1], - dlib::match_endings(".jpeg .jpg .png")); + dlib::match_endings(".jpeg .jpg .png .webp")); dlib::rand rnd;