diff --git a/CMakeLists.txt b/CMakeLists.txt index 43a1d523b..8a1207486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ cmake_minimum_required( VERSION 3.12 FATAL_ERROR ) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../ecbuild/cmake") find_package( ecbuild 3.4 REQUIRED ) -project( magics VERSION 4.15.1 LANGUAGES CXX ) +project( magics VERSION 4.16.0 LANGUAGES CXX ) # make sure that the header files are installed into include/magics # note that this needs to be done before ecbuild_declare_project() @@ -56,6 +56,12 @@ ecbuild_add_option( FEATURE GEOTIFF CONDITION HAVE_CAIRO REQUIRED_PACKAGES GeoTIFF ) +ecbuild_add_option( FEATURE WEBP + DEFAULT OFF + DESCRIPTION "webp support [implies cairo]" + CONDITION HAVE_CAIRO + REQUIRED_PACKAGES WEBP ) + ecbuild_add_option( FEATURE NETCDF DEFAULT ON DESCRIPTION "enable netcdf support" @@ -274,6 +280,12 @@ if( HAVE_GEOTIFF ) list( APPEND MAGICS_EXTRA_DEFINITIONS HAVE_GEOTIFF ) endif() +if( HAVE_WEBP ) + list( APPEND MAGICS_EXTRA_INCLUDE_DIRS ${WEBP_INCLUDE_DIR} ) + list( APPEND MAGICS_EXTRA_LIBRARIES ${WEBP_LIBRARY} ) + list( APPEND MAGICS_EXTRA_DEFINITIONS HAVE_WEBP ) +endif() + list( APPEND MAGICS_EXTRA_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} ) if( EC_OS_NAME MATCHES "windows" ) diff --git a/cmake/FindWebP.cmake b/cmake/FindWebP.cmake new file mode 100644 index 000000000..b22279e49 --- /dev/null +++ b/cmake/FindWebP.cmake @@ -0,0 +1,69 @@ +# (C) Copyright 2023- ECMWF. +# +# This software is licensed under the terms of the Apache Licence Version 2.0 +# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. +# In applying this licence, ECMWF does not waive the privileges and immunities +# granted to it by virtue of its status as an intergovernmental organisation +# nor does it submit to any jurisdiction. + +# - Try to find the WebP includes and library +# This module defines +# +# WEBP_FOUND - System has WebP +# WEBP_INCLUDE_DIRS - the WebP include directories +# WEBP_LIBRARIES - the libraries needed to use WebP +# +# The following paths will be searched with priority if set in CMake or env +# +# WEBP_DIR - root folder of the WebP installation +# WEBP_PATH - root folder of the WebP installation +# +# Define WEBP_MIN_VERSION for which version desired. + +if( NOT WEBP_PATH ) + if ( NOT "$ENV{WEBP_PATH}" STREQUAL "" ) + set( WEBP_PATH "$ENV{WEBP_PATH}" ) + elseif ( NOT "$ENV{WEBP_DIR}" STREQUAL "" ) + set( WEBP_PATH "$ENV{WEBP_DIR}" ) + endif() +endif() + +if( NOT WEBP_PATH ) + + include(FindPkgConfig) + + if(WEBP_MIN_VERSION) + pkg_check_modules(PKWEBP ${_pkgconfig_REQUIRED} QUIET WEBP>=${WEBP_MIN_VERSION}) + else() + pkg_check_modules(PKWEBP ${_pkgconfig_REQUIRED} QUIET WEBP) + endif() + + if( PKG_CONFIG_FOUND AND PKWEBP_FOUND ) + + find_path(WEBP_INCLUDE_DIR encode.h HINTS ${PKWEBP_INCLUDEDIR} ${PKWEBP_INCLUDE_DIRS} PATH_SUFFIXES WEBP NO_DEFAULT_PATH ) + find_library(WEBP_LIBRARY webp HINTS ${PKWEBP_LIBDIR} ${PKWEBP_LIBRARY_DIRS} PATH_SUFFIXES WEBP NO_DEFAULT_PATH ) + + endif() + +endif() + +if( WEBP_PATH ) + + find_path(WEBP_INCLUDE_DIR NAMES encode.h PATHS ${WEBP_PATH} ${WEBP_PATH}/include PATH_SUFFIXES WEBP NO_DEFAULT_PATH ) + find_library(WEBP_LIBRARY NAMES webp PATHS ${WEBP_PATH} ${WEBP_PATH}/lib PATH_SUFFIXES WEBP NO_DEFAULT_PATH ) + +endif() + +find_path(WEBP_INCLUDE_DIR NAMES encode.h PATHS PATH_SUFFIXES WEBP ) +find_library( WEBP_LIBRARY NAMES webp PATHS PATH_SUFFIXES WEBP ) + + +# handle the QUIETLY and REQUIRED arguments and set GRIBAPI_FOUND +include(FindPackageHandleStandardArgs) +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} ) + +mark_as_advanced( WEBP_INCLUDE_DIR WEBP_LIBRARY ) diff --git a/src/common/OutputFactory.cc b/src/common/OutputFactory.cc index b91f8da4c..29447e64c 100644 --- a/src/common/OutputFactory.cc +++ b/src/common/OutputFactory.cc @@ -229,6 +229,14 @@ void CAIRO_GeoTiffOutputFactory::set(DriverManager& magics, const XmlNode& node) magics.push_back(driver); } + +void CAIRO_WebpOutputFactory::set(DriverManager& magics, const XmlNode& node) { + CairoDriver* driver = new CairoDriver(); + driver->set(node); + driver->setWEBP(); + + magics.push_back(driver); +} #endif void KML_KmlOutputFactory::set(DriverManager& magics, const XmlNode& node) { diff --git a/src/common/OutputFactory.h b/src/common/OutputFactory.h index bf3cbc35a..d423835ea 100644 --- a/src/common/OutputFactory.h +++ b/src/common/OutputFactory.h @@ -252,6 +252,15 @@ class CAIRO_GeoTiffOutputFactory : public OutputFactory { virtual OutputFactory* clone() const override { return new CAIRO_GeoTiffOutputFactory(); } virtual void set(DriverManager&, const XmlNode&) override; }; + +class CAIRO_WebpOutputFactory : public OutputFactory { +public: + CAIRO_WebpOutputFactory() {} + virtual ~CAIRO_WebpOutputFactory() override {} + + virtual OutputFactory* clone() const override { return new CAIRO_WebpOutputFactory(); } + virtual void set(DriverManager&, const XmlNode&) override; +}; #endif class KML_KmlOutputFactory : public OutputFactory { diff --git a/src/common/OutputHandler.cc b/src/common/OutputHandler.cc index 42115be46..c039ca2b9 100644 --- a/src/common/OutputHandler.cc +++ b/src/common/OutputHandler.cc @@ -92,7 +92,7 @@ void OutputHandler::set(const XmlNode& node, DriverManager& magics) { void OutputHandler::drivers(vector& ds) { vector all = {"ps", "eps", "ps_pdf", "gd_png", "jpeg", "gif", "gif_animation", "svg", "mgb", "png", "pdf", "cairo", "cairo_svg", "cairo_ps", - "cairo_eps", "geotiff", "kml", "geojson"}; + "cairo_eps", "geotiff", "webp", "kml", "geojson"}; for (const auto& d : all) { try { @@ -142,6 +142,9 @@ static SimpleObjectMaker ceps("cairo_eps" #ifdef HAVE_GEOTIFF static SimpleObjectMaker geotiff("geotiff"); #endif +#ifdef HAVE_WEBP +static SimpleObjectMaker webp("webp"); +#endif #endif static SimpleObjectMaker kml("kml"); diff --git a/src/drivers/CairoDriver.cc b/src/drivers/CairoDriver.cc index d610f67a4..4c3c2be9f 100644 --- a/src/drivers/CairoDriver.cc +++ b/src/drivers/CairoDriver.cc @@ -120,7 +120,7 @@ void CairoDriver::open() { void CairoDriver::setupNewSurface() const { - if (magCompare(backend_, "png") || magCompare(backend_, "geotiff")) { + if (magCompare(backend_, "png") || magCompare(backend_, "geotiff") || magCompare(backend_, "webp")) { surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dimensionXglobal_, dimensionYglobal_); } else if (magCompare(backend_, "pdf")) { @@ -206,7 +206,7 @@ void CairoDriver::setupNewSurface() const { #endif } - if (magCompare(transparent_, "off") || !(magCompare(backend_, "png") || magCompare(backend_, "geotiff"))) { + if (magCompare(transparent_, "off") || !(magCompare(backend_, "png") || magCompare(backend_, "geotiff")|| magCompare(backend_, "webp"))) { cairo_set_source_rgb(cr_, 1.0, 1.0, 1.0); /* white */ } else { @@ -243,7 +243,7 @@ void CairoDriver::close() { */ MAGICS_NO_EXPORT void CairoDriver::startPage() const { if (currentPage_ > 0) { - if (magCompare(backend_, "png") || magCompare(backend_, "geotiff")) { + if (magCompare(backend_, "png") || magCompare(backend_, "geotiff") || magCompare(backend_, "webp")) { cairo_destroy(cr_); cairo_surface_destroy(surface_); @@ -342,6 +342,14 @@ MAGICS_NO_EXPORT void CairoDriver::endPage() const { write_tiff(); #else MagLog::error() << "CairoDriver: GEOTIFF not enabled!" << std::endl; +#endif + } + else if (magCompare(backend_, "webp")) { +#ifdef HAVE_WEBP + fileName_ = getFileName("webp", currentPage_); + write_webp(); +#else + MagLog::error() << "CairoDriver: WebP not enabled!" << std::endl; #endif } } @@ -360,6 +368,103 @@ void CairoDriver::closeLayer(Layer&) const { cairo_restore(cr_); } +#ifdef HAVE_WEBP + +#include +/*! + \brief write raster into WebP + + Only the raw raster (normally written to a PNG) is here written into a Webp. + +*/ +MAGICS_NO_EXPORT void CairoDriver::write_webp() const { + + WebPPicture picture; + uint32_t* argb_output; + + int x, y; + + const int width = cairo_image_surface_get_width(surface_); + const int height = cairo_image_surface_get_height(surface_); + const int stride = cairo_image_surface_get_stride(surface_); + const cairo_format_t format = cairo_image_surface_get_format(surface_); + unsigned char* data = cairo_image_surface_get_data(surface_); + + if (format != CAIRO_FORMAT_RGB24 && format != CAIRO_FORMAT_ARGB32) { + MagLog::error() << "CairoDriver: invalid Cairo image format. Not able to create WebP. "<< fileName_ << std::endl; + return; + } + + // Configure WebP - especially compression + WebPConfig config; + if (!WebPConfigPreset(&config, WEBP_PRESET_DRAWING, quality_)) + return; + config.lossless = 1; + config.quality = 90; + config.thread_level = 1; + config.method = 2; // Compression method (0=fast/larger, 6=slow/smaller) + + if (!WebPValidateConfig(&config)) {return;} + if (!WebPPictureInit(&picture)) {return;} + picture.use_argb = 1; + picture.width = width; + picture.height = height; + + if (!WebPPictureAlloc(&picture)) {return;} + + // Set up write-to-memory + WebPMemoryWriter writer; + WebPMemoryWriterInit(&writer); + picture.writer = WebPMemoryWrite; + picture.custom_ptr = &writer; + + // Copy image data into WebP picture + argb_output = picture.argb; + for (y = 0; y < height; y++) { + + // Get pixels at start of each row + uint32_t* src = (uint32_t*) data; + uint32_t* dst = argb_output; + + // For each pixel in row + for (x = 0; x < width; x++) { + + // Pull pixel data, removing alpha channel if necessary + uint32_t src_pixel = *src; + if (format != CAIRO_FORMAT_ARGB32) + src_pixel |= 0xFF000000; + *dst = src_pixel; + src++; + dst++; + } + // Next row + data += stride; + argb_output += picture.argb_stride; + } + + ////// Encode image + const int result = WebPEncode(&config, &picture); + if (result < 1) { + MagLog::error() << "CairoDriver: Encoding error: "<