Add webp support (#2565)

* Add BGR(A) to pixel_traits

* add support for reading webp

* Apply Davis' suggestions and fix formatting

* Fix signed/unsigned warning

* Update decoding paths

* update pixel traits documentation

* Add support for writing WebP images

* Simplify image_saver code

* WIP: add tests, PSNR is low but images look good

* Add lossless compression for quality > 100

* Fix build when WebP support is disabled

* Use C++ stream instead of C-style FILE

* Fix indentation

* Use reinterpret_cast instead of C-style cast

* Improve impl::impl_save_webp signature

* Remove empty line

* Use switch statement and clean up code

* Update Copyright and test libwebp on Linux

* Fix formatting in github workflow

* Fix operator== for bgr_alpha_pixel

* Show where the test fails

* Add libwebp to CI for the remaining Linux workflows

* Use filename consistently

* Improve message with wrong pixel type

* Fix tests for WebP images

* Prevent saving images which are too large and improve error messages

* Use max dimension from WebP header directly

* Update documentation, index and release notes

* Update dlib/image_saver/save_webp_abstract.h

Co-authored-by: Martin T. H. Sandsmark <martin.sandsmark@kde.org>
Co-authored-by: Davis E. King <davis685@gmail.com>
pull/2571/head
Adrià Arrufat 2 years ago committed by GitHub
parent 0aa8b4cbfc
commit a76f205bf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

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

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

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

@ -7,6 +7,7 @@
#include "../string.h"
#include "png_loader.h"
#include "jpeg_loader.h"
#include "webp_loader.h"
#include "image_loader.h"
#include <fstream>
#include <sstream>
@ -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:
{

@ -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 <webp/decode.h>
#include <fstream>
namespace dlib
{
static std::vector<unsigned char> 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<unsigned char> buffer(stream.tellg());
stream.seekg(0);
stream.read(reinterpret_cast<char*>(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_

@ -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 <vector>
#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<typename image_type>
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<image_type> vimg(image);
vimg.set_size(height_, width_);
typedef typename image_traits<image_type>::pixel_type pixel_type;
unsigned char* output = reinterpret_cast<unsigned char*>(image_data(vimg));
const int stride = width_step(vimg);
const size_t output_size = stride * height_;
if (pixel_traits<pixel_type>::rgb_alpha)
{
if (pixel_traits<pixel_type>::bgr_layout)
read_bgra(output, output_size, stride);
else
read_rgba(output, output_size, stride);
return;
}
if (pixel_traits<pixel_type>::rgb)
{
if (pixel_traits<pixel_type>::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<rgb_alpha_pixel> decoded;
decoded.set_size(height_, width_);
unsigned char* output_dec = reinterpret_cast<unsigned char*>(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<unsigned char> 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<const unsigned char*>(imgbuff), imgbuffsize).get_image(image);
}
// ----------------------------------------------------------------------------------------
}
#ifdef NO_MAKEFILE
#include "webp_loader.cpp"
#endif
#endif // DLIB_WEBP_IMPORT

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

@ -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 <sstream>
#include <webp/encode.h>
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<char*>(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_

@ -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 <string>
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<is_matrix<image_type>>::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<image_type> img(img_);
using pixel_type = typename image_traits<image_type>::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<const uint8_t*>(image_data(img));
const int width = img.nc();
const int height = img.nr();
const int stride = width_step(img);
if (pixel_traits<pixel_type>::rgb_alpha)
{
if (pixel_traits<pixel_type>::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<pixel_type>::rgb)
{
if (pixel_traits<pixel_type>::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<rgb_pixel> temp;
assign_image(temp, img);
auto data = reinterpret_cast<const uint8_t*>(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<EXP>& img,
const std::string& filename,
float quality = 75
)
{
array2d<typename EXP::type> temp;
assign_image(temp, img);
save_webp(temp, filename, quality);
}
// ----------------------------------------------------------------------------------------
}
#endif // DLIB_WEBP_SUPPORT

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

@ -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<rgb_pixel>
{
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<bgr_pixel>
{
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<rgb_alpha_pixel>
{
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<bgr_alpha_pixel>
{
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<hsi_pixel>
{
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<lab_pixel>
{
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;

@ -2306,6 +2306,119 @@ namespace
DLIB_TEST(image == result);
}
template <typename pixel_type>
double psnr(const matrix<pixel_type>& img1, const matrix<pixel_type>& 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<const uint8_t*>(image_data(img1));
auto data2 = reinterpret_cast<const uint8_t*>(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<double>(data1[i + k]) - static_cast<double>(data2[i + k]), 2);
}
mse /= data_size;
return 20 * std::log10(pixel_traits<pixel_type>::max()) - 10 * std::log10(mse);
}
void test_webp()
{
#ifdef DLIB_WEBP_SUPPORT
print_spinner();
matrix<rgb_pixel> rgb_img, rgb_dec;
matrix<rgb_alpha_pixel> rgba_img, rgba_dec;
matrix<bgr_pixel> bgr_img, bgr_dec;
matrix<bgr_alpha_pixel> bgra_img, bgra_dec;
// this is a matrix<rgb_alpha_pixel> 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<double>::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<double>::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<double>::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<double>::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;

@ -68,6 +68,7 @@
<item>rgb_pixel</item>
<item>bgr_pixel</item>
<item>rgb_alpha_pixel</item>
<item>bgr_alpha_pixel</item>
<item>hsi_pixel</item>
<item>lab_pixel</item>
<item>pixel_traits</item>
@ -89,6 +90,9 @@
<item>save_dng</item>
<item>save_png</item>
<item>save_jpeg</item>
<item>webp_loader</item>
<item>load_webp</item>
<item>save_webp</item>
</section>
<section>
@ -852,7 +856,27 @@
</description>
</component>
<!-- ************************************************************************* -->
<component>
<name>bgr_alpha_pixel</name>
<file>dlib/pixel.h</file>
<spec_file link="true">dlib/pixel.h</spec_file>
<description>
This is a simple struct that represents an BGR colored graphical pixel with an
alpha channel.
<p>
The difference between this object and the <a href="#rgb_alpha_pixel">rgb_alpha_pixel</a>
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 <a href="#cv_image">cv_image</a> object to map an OpenCV image
into a more object oriented form.
</p>
</description>
</component>
<!-- ************************************************************************* -->
<component>
@ -1067,7 +1091,26 @@
</description>
</component>
<!-- ************************************************************************* -->
<component>
<name>webp_loader</name>
<file>dlib/image_io.h</file>
<spec_file link="true">dlib/image_loader/webp_loader_abstract.h</spec_file>
<description>
This object loads a WebP image file into
an <a href="containers.html#array2d">array2d</a> of <a href="dlib/pixel.h.html">pixels</a>.
<p>
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.
</p>
</description>
</component>
<!-- ************************************************************************* -->
<component>
@ -1078,7 +1121,7 @@
This function loads a JPEG image file into
an <a href="containers.html#array2d">array2d</a> of <a href="dlib/pixel.h.html">pixels</a>.
<p>
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 @@
</component>
<!-- ************************************************************************* -->
<component>
<name>load_webp</name>
<file>dlib/image_io.h</file>
<spec_file link="true">dlib/image_loader/image_loader_abstract.h</spec_file>
<description>
This function loads a WebP image file into
an <a href="containers.html#array2d">array2d</a> of <a href="dlib/pixel.h.html">pixels</a>.
<p>
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.
</p>
</description>
</component>
<!-- ************************************************************************* -->
<component>
@ -1169,7 +1230,32 @@
</description>
</component>
<!-- ************************************************************************* -->
<component>
<name>save_webp</name>
<file>dlib/image_io.h</file>
<spec_file link="true">dlib/image_saver/save_webp_abstract.h</spec_file>
<description>
This global function writes an image to disk as a WebP file.
<p>
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.
</p>
<p>
This routine can save images containing any type of pixel. However, save_webp() can
only natively store the following pixel types: <b>rgb_pixel</b>,
<b>rgb_alpha_pixel</b>, <b>bgr_pixel</b>, and <b>bgr_alpha_pixel</b>.
All other pixel types will be converted into one of these types as appropriate
before being saved to disk.
</p>
</description>
</component>
<!-- ************************************************************************* -->
<component>
@ -1219,7 +1305,7 @@
This function loads a Portable Network Graphics (PNG) image file into
an <a href="containers.html#array2d">array2d</a> of <a href="dlib/pixel.h.html">pixels</a>.
<p>
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.

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

@ -1529,6 +1529,7 @@
<term file="imaging.html" name="load_png" include="dlib/image_io.h"/>
<term file="imaging.html" name="load_jpeg" include="dlib/image_io.h"/>
<term file="imaging.html" name="load_dng" include="dlib/image_io.h"/>
<term file="imaging.html" name="load_webp" include="dlib/image_io.h"/>
<term file="imaging.html" name="pixel_traits" include="dlib/pixel.h"/>
<term file="imaging.html" name="rgb_pixel" include="dlib/pixel.h"/>
<term file="imaging.html" name="bgr_pixel" include="dlib/pixel.h"/>
@ -1536,12 +1537,14 @@
<term file="imaging.html" name="toMat" include="dlib/opencv.h"/>
<term link="imaging.html#cv_image" name="OpenCV Image"/>
<term file="imaging.html" name="rgb_alpha_pixel" include="dlib/pixel.h"/>
<term file="imaging.html" name="bgr_alpha_pixel" include="dlib/pixel.h"/>
<term link="imaging.html#rgb_alpha_pixel" name="alpha"/>
<term file="imaging.html" name="save_bmp" include="dlib/image_io.h"/>
<term file="imaging.html" name="save_png" include="dlib/image_io.h"/>
<term file="imaging.html" name="save_jpeg" include="dlib/image_io.h"/>
<term file="imaging.html" name="load_image" include="dlib/image_io.h"/>
<term file="imaging.html" name="save_dng" include="dlib/image_io.h"/>
<term file="imaging.html" name="save_webp" include="dlib/image_io.h"/>
<term file="dlib/image_transforms/spatial_filtering_abstract.h.html" name="create_gaussian_filter" include="dlib/image_transforms.h"/>
<term name="gaussian">
<term link="dlib/image_transforms/spatial_filtering_abstract.h.html#gaussian" name="1D Gaussian" include="dlib/image_transforms.h"/>
@ -1569,6 +1572,7 @@
<term file="imaging.html" name="partition_pixels" include="dlib/image_transforms.h"/>
<term file="imaging.html" name="png_loader" include="dlib/image_io.h"/>
<term file="imaging.html" name="jpeg_loader" include="dlib/image_io.h"/>
<term file="imaging.html" name="webp_loader" include="dlib/image_io.h"/>
<term file="imaging.html" name="interpolate_nearest_neighbor" include="dlib/image_transforms.h"/>
<term file="imaging.html" name="interpolate_bilinear" include="dlib/image_transforms.h"/>

@ -61,7 +61,7 @@ int main(int argc, char** argv) try
// Find supported image files.
const std::vector<file> 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;

Loading…
Cancel
Save