webp encoding/decoding support - refs #1955
This commit is contained in:
parent
db39528231
commit
a141c5c27d
17 changed files with 630 additions and 11 deletions
16
INSTALL.md
16
INSTALL.md
|
@ -48,22 +48,26 @@ Mapnik Core depends on:
|
||||||
- regex (optionally built with icu regex support)
|
- regex (optionally built with icu regex support)
|
||||||
- program_options (optionally for mapnik command line programs)
|
- program_options (optionally for mapnik command line programs)
|
||||||
* libicuuc >= 4.0 (ideally >= 4.2) - International Components for Unicode
|
* libicuuc >= 4.0 (ideally >= 4.2) - International Components for Unicode
|
||||||
* libpng >= 1.2.x - PNG graphics
|
|
||||||
* libjpeg - JPEG graphics
|
|
||||||
* libtiff - TIFF graphics
|
|
||||||
* libz - Zlib compression
|
* libz - Zlib compression
|
||||||
* libfreetype - Freetype2 for font support (Install requires freetype-config)
|
* libfreetype - Freetype2 for font support (Install requires freetype-config)
|
||||||
* libxml2 - XML parsing (Install requires xml2-config)
|
* libxml2 - XML parsing (Install requires xml2-config)
|
||||||
* libproj - PROJ.4 projection library
|
|
||||||
|
Mapnik Core optionally depends on:
|
||||||
|
|
||||||
|
* libpng >= 1.2.x - PNG graphics (Default enabled, if found)
|
||||||
|
* libjpeg - JPEG graphics (Default enabled, if found)
|
||||||
|
* libtiff - TIFF graphics (Default enabled, if found)
|
||||||
|
* libwebp - WEBP graphics (Default enabled, if found)
|
||||||
|
* libproj - PROJ.4 projection library (Default enabled, if found)
|
||||||
|
|
||||||
Mapnik Python bindings depend on:
|
Mapnik Python bindings depend on:
|
||||||
|
|
||||||
* Python 2.5-2.7 or >= 3.2
|
* Python 2.5-2.7 or >= 3.2
|
||||||
* Boost python
|
* Boost python
|
||||||
|
|
||||||
Note: Python3k is supported, see: https://github.com/mapnik/mapnik/wiki/Python3k
|
Note: Python 3.x is supported, see: https://github.com/mapnik/mapnik/wiki/Python3k
|
||||||
|
|
||||||
Optional dependencies:
|
Additional optional dependencies:
|
||||||
|
|
||||||
* Cairo >= 1.6.0 - Graphics library for output formats like PDF, PS, and SVG
|
* Cairo >= 1.6.0 - Graphics library for output formats like PDF, PS, and SVG
|
||||||
- pkg-config - Required for building with cairo support
|
- pkg-config - Required for building with cairo support
|
||||||
|
|
22
SConstruct
22
SConstruct
|
@ -73,6 +73,7 @@ pretty_dep_names = {
|
||||||
'jpeg':'JPEG C library | configure with JPEG_LIBS & JPEG_INCLUDES',
|
'jpeg':'JPEG C library | configure with JPEG_LIBS & JPEG_INCLUDES',
|
||||||
'tiff':'TIFF C library | configure with TIFF_LIBS & TIFF_INCLUDES',
|
'tiff':'TIFF C library | configure with TIFF_LIBS & TIFF_INCLUDES',
|
||||||
'png':'PNG C library | configure with PNG_LIBS & PNG_INCLUDES',
|
'png':'PNG C library | configure with PNG_LIBS & PNG_INCLUDES',
|
||||||
|
'webp':'WEBP C library | configure with WEBP_LIBS & WEBP_INCLUDES',
|
||||||
'icuuc':'ICU C++ library | configure with ICU_LIBS & ICU_INCLUDES or use ICU_LIB_NAME to specify custom lib name | more info: http://site.icu-project.org/',
|
'icuuc':'ICU C++ library | configure with ICU_LIBS & ICU_INCLUDES or use ICU_LIB_NAME to specify custom lib name | more info: http://site.icu-project.org/',
|
||||||
'z':'Z compression library | more info: http://www.zlib.net/',
|
'z':'Z compression library | more info: http://www.zlib.net/',
|
||||||
'm':'Basic math library, part of C++ stlib',
|
'm':'Basic math library, part of C++ stlib',
|
||||||
|
@ -325,6 +326,9 @@ opts.AddVariables(
|
||||||
BoolVariable('TIFF', 'Build Mapnik with TIFF read and write support', 'True'),
|
BoolVariable('TIFF', 'Build Mapnik with TIFF read and write support', 'True'),
|
||||||
PathVariable('TIFF_INCLUDES', 'Search path for libtiff include files', '/usr/include', PathVariable.PathAccept),
|
PathVariable('TIFF_INCLUDES', 'Search path for libtiff include files', '/usr/include', PathVariable.PathAccept),
|
||||||
PathVariable('TIFF_LIBS', 'Search path for libtiff library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
PathVariable('TIFF_LIBS', 'Search path for libtiff library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
||||||
|
BoolVariable('WEBP', 'Build Mapnik with WEBP read', 'True'),
|
||||||
|
PathVariable('WEBP_INCLUDES', 'Search path for libwebp include files', '/usr/include', PathVariable.PathAccept),
|
||||||
|
PathVariable('WEBP_LIBS','Search path for libwebp library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
||||||
BoolVariable('PROJ', 'Build Mapnik with proj4 support to enable transformations between many different projections', 'True'),
|
BoolVariable('PROJ', 'Build Mapnik with proj4 support to enable transformations between many different projections', 'True'),
|
||||||
PathVariable('PROJ_INCLUDES', 'Search path for PROJ.4 include files', '/usr/include', PathVariable.PathAccept),
|
PathVariable('PROJ_INCLUDES', 'Search path for PROJ.4 include files', '/usr/include', PathVariable.PathAccept),
|
||||||
PathVariable('PROJ_LIBS', 'Search path for PROJ.4 library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
PathVariable('PROJ_LIBS', 'Search path for PROJ.4 library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
||||||
|
@ -1185,7 +1189,7 @@ if not preconfigured:
|
||||||
|
|
||||||
if env['JPEG']:
|
if env['JPEG']:
|
||||||
env.Append(CPPDEFINES = '-DHAVE_JPEG')
|
env.Append(CPPDEFINES = '-DHAVE_JPEG')
|
||||||
LIBSHEADERS.append(['jpeg', ['stdio.h', 'jpeglib.h'], True,'C'])
|
LIBSHEADERS.append(['jpeg', ['stdio.h', 'jpeglib.h'], False,'C'])
|
||||||
inc_path = env['%s_INCLUDES' % 'JPEG']
|
inc_path = env['%s_INCLUDES' % 'JPEG']
|
||||||
lib_path = env['%s_LIBS' % 'JPEG']
|
lib_path = env['%s_LIBS' % 'JPEG']
|
||||||
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
||||||
|
@ -1195,7 +1199,7 @@ if not preconfigured:
|
||||||
|
|
||||||
if env['PROJ']:
|
if env['PROJ']:
|
||||||
env.Append(CPPDEFINES = '-DMAPNIK_USE_PROJ4')
|
env.Append(CPPDEFINES = '-DMAPNIK_USE_PROJ4')
|
||||||
LIBSHEADERS.append(['proj', 'proj_api.h', True,'C'])
|
LIBSHEADERS.append(['proj', 'proj_api.h', False,'C'])
|
||||||
inc_path = env['%s_INCLUDES' % 'PROJ']
|
inc_path = env['%s_INCLUDES' % 'PROJ']
|
||||||
lib_path = env['%s_LIBS' % 'PROJ']
|
lib_path = env['%s_LIBS' % 'PROJ']
|
||||||
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
||||||
|
@ -1205,7 +1209,7 @@ if not preconfigured:
|
||||||
|
|
||||||
if env['PNG']:
|
if env['PNG']:
|
||||||
env.Append(CPPDEFINES = '-DHAVE_PNG')
|
env.Append(CPPDEFINES = '-DHAVE_PNG')
|
||||||
LIBSHEADERS.append(['png', 'png.h', True,'C'])
|
LIBSHEADERS.append(['png', 'png.h', False,'C'])
|
||||||
inc_path = env['%s_INCLUDES' % 'PNG']
|
inc_path = env['%s_INCLUDES' % 'PNG']
|
||||||
lib_path = env['%s_LIBS' % 'PNG']
|
lib_path = env['%s_LIBS' % 'PNG']
|
||||||
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
||||||
|
@ -1213,9 +1217,19 @@ if not preconfigured:
|
||||||
else:
|
else:
|
||||||
env['SKIPPED_DEPS'].extend(['png'])
|
env['SKIPPED_DEPS'].extend(['png'])
|
||||||
|
|
||||||
|
if env['WEBP']:
|
||||||
|
env.Append(CPPDEFINES = '-DHAVE_WEBP')
|
||||||
|
LIBSHEADERS.append(['webp', 'webp/decode.h', False,'C'])
|
||||||
|
inc_path = env['%s_INCLUDES' % 'WEBP']
|
||||||
|
lib_path = env['%s_LIBS' % 'WEBP']
|
||||||
|
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
||||||
|
env.AppendUnique(LIBPATH = os.path.realpath(lib_path))
|
||||||
|
else:
|
||||||
|
env['SKIPPED_DEPS'].extend(['webp'])
|
||||||
|
|
||||||
if env['TIFF']:
|
if env['TIFF']:
|
||||||
env.Append(CPPDEFINES = '-DHAVE_TIFF')
|
env.Append(CPPDEFINES = '-DHAVE_TIFF')
|
||||||
LIBSHEADERS.append(['tiff', 'tiff.h', True,'C'])
|
LIBSHEADERS.append(['tiff', 'tiff.h', False,'C'])
|
||||||
inc_path = env['%s_INCLUDES' % 'TIFF']
|
inc_path = env['%s_INCLUDES' % 'TIFF']
|
||||||
lib_path = env['%s_LIBS' % 'TIFF']
|
lib_path = env['%s_LIBS' % 'TIFF']
|
||||||
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
env.AppendUnique(CPPPATH = os.path.realpath(inc_path))
|
||||||
|
|
|
@ -147,6 +147,12 @@ inline bool is_ps (std::string const& filename)
|
||||||
return boost::algorithm::iends_with(filename,std::string(".ps"));
|
return boost::algorithm::iends_with(filename,std::string(".ps"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool is_webp (std::string const& filename)
|
||||||
|
{
|
||||||
|
return boost::algorithm::iends_with(filename,std::string(".webp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
inline boost::optional<std::string> type_from_filename(std::string const& filename)
|
inline boost::optional<std::string> type_from_filename(std::string const& filename)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -157,6 +163,7 @@ inline boost::optional<std::string> type_from_filename(std::string const& filena
|
||||||
if (is_pdf(filename)) return result_type("pdf");
|
if (is_pdf(filename)) return result_type("pdf");
|
||||||
if (is_svg(filename)) return result_type("svg");
|
if (is_svg(filename)) return result_type("svg");
|
||||||
if (is_ps(filename)) return result_type("ps");
|
if (is_ps(filename)) return result_type("ps");
|
||||||
|
if (is_webp(filename)) return result_type("webp");
|
||||||
return result_type();
|
return result_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,12 @@ public:
|
||||||
{
|
{
|
||||||
return data_.getRow(row + y_) + x_;
|
return data_.getRow(row + y_) + x_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline char const* getBytes() const
|
||||||
|
{
|
||||||
|
return reinterpret_cast<char const*>(&data_);
|
||||||
|
}
|
||||||
|
|
||||||
inline T& data()
|
inline T& data()
|
||||||
{
|
{
|
||||||
return data_;
|
return data_;
|
||||||
|
@ -107,4 +113,3 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // MAPNIK_IMAGE_VIEW_HPP
|
#endif // MAPNIK_IMAGE_VIEW_HPP
|
||||||
|
|
||||||
|
|
130
include/mapnik/webp_io.hpp
Normal file
130
include/mapnik/webp_io.hpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
*
|
||||||
|
* This file is part of Mapnik (c++ mapping toolkit)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Artem Pavlenko
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef MAPNIK_WEBP_IO_HPP
|
||||||
|
#define MAPNIK_WEBP_IO_HPP
|
||||||
|
|
||||||
|
#include <webp/encode.h>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
|
namespace mapnik {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
int webp_stream_write(const uint8_t* data, size_t data_size, const WebPPicture* picture)
|
||||||
|
{
|
||||||
|
T* out = static_cast<T*>(picture->custom_ptr);
|
||||||
|
out->write(reinterpret_cast<const char*>(data), data_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string webp_encoding_error(WebPEncodingError error) {
|
||||||
|
std::ostringstream os;
|
||||||
|
switch (error) {
|
||||||
|
case VP8_ENC_ERROR_OUT_OF_MEMORY: os << "memory error allocating objects"; break;
|
||||||
|
case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY: os << "memory error while flushing bits"; break;
|
||||||
|
case VP8_ENC_ERROR_NULL_PARAMETER: os << "a pointer parameter is NULL"; break;
|
||||||
|
case VP8_ENC_ERROR_INVALID_CONFIGURATION: os << "configuration is invalid"; break;
|
||||||
|
case VP8_ENC_ERROR_BAD_DIMENSION: os << "picture has invalid width/height"; break;
|
||||||
|
case VP8_ENC_ERROR_PARTITION0_OVERFLOW: os << "partition is bigger than 512k"; break;
|
||||||
|
case VP8_ENC_ERROR_PARTITION_OVERFLOW: os << "partition is bigger than 16M"; break;
|
||||||
|
case VP8_ENC_ERROR_BAD_WRITE: os << "error while flushing bytes"; break;
|
||||||
|
case VP8_ENC_ERROR_FILE_TOO_BIG: os << "file is bigger than 4G"; break;
|
||||||
|
default: os << "unknown error (" << error << ")"; break;
|
||||||
|
}
|
||||||
|
os << " during encoding";
|
||||||
|
return os.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
void save_as_webp(T1& file,
|
||||||
|
float quality,
|
||||||
|
int method,
|
||||||
|
int lossless,
|
||||||
|
int image_hint,
|
||||||
|
T2 const& image)
|
||||||
|
{
|
||||||
|
WebPConfig config;
|
||||||
|
if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, quality))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("version mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add additional tuning
|
||||||
|
if (method >= 0) config.method = method;
|
||||||
|
#if (WEBP_ENCODER_ABI_VERSION >> 8) >= 2
|
||||||
|
config.lossless = lossless;
|
||||||
|
config.image_hint = static_cast<WebPImageHint>(image_hint);
|
||||||
|
#else
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma NOTE(compiling against webp that does not support lossless flag)
|
||||||
|
#else
|
||||||
|
#warning "compiling against webp that does not support lossless flag"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool valid = WebPValidateConfig(&config);
|
||||||
|
if (!valid) {
|
||||||
|
throw std::runtime_error("Invalid configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
WebPPicture pic;
|
||||||
|
if (!WebPPictureInit(&pic))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("version mismatch");
|
||||||
|
}
|
||||||
|
pic.width = image.width();
|
||||||
|
pic.height = image.height();
|
||||||
|
#if (WEBP_ENCODER_ABI_VERSION >> 8) >= 2
|
||||||
|
pic.use_argb = 1;
|
||||||
|
#endif
|
||||||
|
if (!WebPPictureAlloc(&pic))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("memory error");
|
||||||
|
}
|
||||||
|
|
||||||
|
int stride = sizeof(typename T2::pixel_type) * image.width();
|
||||||
|
uint8_t const* bytes = reinterpret_cast<uint8_t const*>(image.getBytes());
|
||||||
|
int ok = WebPPictureImportRGBA(&pic, bytes, stride);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(webp_encoding_error(pic.error_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
pic.writer = webp_stream_write<T1>;
|
||||||
|
pic.custom_ptr = &file;
|
||||||
|
|
||||||
|
ok = WebPEncode(&config, &pic);
|
||||||
|
WebPPictureFree(&pic);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(webp_encoding_error(pic.error_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
file.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MAPNIK_WEBP_IO_HPP
|
|
@ -68,6 +68,9 @@ if env['PNG']:
|
||||||
if env['TIFF']:
|
if env['TIFF']:
|
||||||
lib_env['LIBS'].append('tiff')
|
lib_env['LIBS'].append('tiff')
|
||||||
|
|
||||||
|
if env['WEBP']:
|
||||||
|
lib_env['LIBS'].append('webp')
|
||||||
|
|
||||||
if env['JPEG']:
|
if env['JPEG']:
|
||||||
lib_env['LIBS'].append('jpeg')
|
lib_env['LIBS'].append('jpeg')
|
||||||
|
|
||||||
|
@ -269,6 +272,12 @@ if env['PNG']:
|
||||||
png_reader.cpp
|
png_reader.cpp
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
if env['WEBP']:
|
||||||
|
source += Split(
|
||||||
|
"""
|
||||||
|
webp_reader.cpp
|
||||||
|
""")
|
||||||
|
|
||||||
# agg backend
|
# agg backend
|
||||||
source += Split(
|
source += Split(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -59,6 +59,14 @@ inline boost::optional<std::string> type_from_bytes(char const* data, size_t siz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size>=12)
|
||||||
|
{
|
||||||
|
if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F' &&
|
||||||
|
data[8] == 'W' && data[9] == 'E' && data[10] == 'B' && data[11] == 'P')
|
||||||
|
{
|
||||||
|
return result_type("webp");
|
||||||
|
}
|
||||||
|
}
|
||||||
return result_type();
|
return result_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,10 @@ extern "C"
|
||||||
#include <mapnik/jpeg_io.hpp>
|
#include <mapnik/jpeg_io.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_WEBP)
|
||||||
|
#include <mapnik/webp_io.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <mapnik/image_util.hpp>
|
#include <mapnik/image_util.hpp>
|
||||||
#include <mapnik/image_data.hpp>
|
#include <mapnik/image_data.hpp>
|
||||||
#include <mapnik/graphics.hpp>
|
#include <mapnik/graphics.hpp>
|
||||||
|
@ -237,6 +241,73 @@ void handle_png_options(std::string const& type,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(HAVE_WEBP)
|
||||||
|
void handle_webp_options(std::string const& type,
|
||||||
|
double & quality,
|
||||||
|
int & method,
|
||||||
|
int & lossless,
|
||||||
|
int & image_hint
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (type == "webp")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type.length() > 4){
|
||||||
|
boost::char_separator<char> sep(":");
|
||||||
|
boost::tokenizer< boost::char_separator<char> > tokens(type, sep);
|
||||||
|
BOOST_FOREACH(std::string t, tokens)
|
||||||
|
{
|
||||||
|
if (boost::algorithm::starts_with(t, "quality="))
|
||||||
|
{
|
||||||
|
std::string val = t.substr(8);
|
||||||
|
if (!val.empty())
|
||||||
|
{
|
||||||
|
if (!mapnik::util::string2double(val,quality) || quality < 0.0 || quality > 100.0)
|
||||||
|
{
|
||||||
|
throw ImageWriterException("invalid webp quality: '" + val + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (boost::algorithm::starts_with(t, "method="))
|
||||||
|
{
|
||||||
|
std::string val = t.substr(7);
|
||||||
|
if (!val.empty())
|
||||||
|
{
|
||||||
|
if (!mapnik::util::string2int(val,method) || method < 0 || method > 6)
|
||||||
|
{
|
||||||
|
throw ImageWriterException("invalid webp method: '" + val + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (boost::algorithm::starts_with(t, "lossless="))
|
||||||
|
{
|
||||||
|
std::string val = t.substr(9);
|
||||||
|
if (!val.empty())
|
||||||
|
{
|
||||||
|
if (!mapnik::util::string2int(val,lossless) || lossless < 0 || lossless > 1)
|
||||||
|
{
|
||||||
|
throw ImageWriterException("invalid webp lossless: '" + val + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (boost::algorithm::starts_with(t, "image_hint="))
|
||||||
|
{
|
||||||
|
std::string val = t.substr(11);
|
||||||
|
if (!val.empty())
|
||||||
|
{
|
||||||
|
if (!mapnik::util::string2int(val,image_hint) || image_hint < 0 || image_hint > 3)
|
||||||
|
{
|
||||||
|
throw ImageWriterException("invalid webp image_hint: '" + val + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void save_to_stream(T const& image,
|
void save_to_stream(T const& image,
|
||||||
std::ostream & stream,
|
std::ostream & stream,
|
||||||
|
@ -369,6 +440,26 @@ void save_to_stream(T const& image,
|
||||||
save_as_jpeg(stream, quality, image);
|
save_as_jpeg(stream, quality, image);
|
||||||
#else
|
#else
|
||||||
throw ImageWriterException("jpeg output is not enabled in your build of Mapnik");
|
throw ImageWriterException("jpeg output is not enabled in your build of Mapnik");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (boost::algorithm::starts_with(t, "webp"))
|
||||||
|
{
|
||||||
|
#if defined(HAVE_WEBP)
|
||||||
|
double quality = 90.0; // 0 lowest, 100 highest
|
||||||
|
int method = 3; // 0 if fastest, 6 slowest
|
||||||
|
int lossless = 0; // Lossless encoding (0=lossy(default), 1=lossless).
|
||||||
|
int image_hint = 3; // used when lossless=1
|
||||||
|
/*
|
||||||
|
WEBP_HINT_DEFAULT = 0, // default preset.
|
||||||
|
WEBP_HINT_PICTURE, // digital picture, like portrait, inner shot
|
||||||
|
WEBP_HINT_PHOTO, // outdoor photograph, with natural lighting
|
||||||
|
WEBP_HINT_GRAPH, // Discrete tone image (graph, map-tile etc).
|
||||||
|
WEBP_HINT_LAST
|
||||||
|
*/
|
||||||
|
handle_webp_options(t,quality,method,lossless, image_hint);
|
||||||
|
save_as_webp(stream, static_cast<float>(quality), method, lossless, image_hint, image);
|
||||||
|
#else
|
||||||
|
throw ImageWriterException("webp output is not enabled in your build of Mapnik");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else throw ImageWriterException("unknown file type: " + type);
|
else throw ImageWriterException("unknown file type: " + type);
|
||||||
|
|
239
src/webp_reader.cpp
Normal file
239
src/webp_reader.cpp
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
*
|
||||||
|
* This file is part of Mapnik (c++ mapping toolkit)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013 Artem Pavlenko
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
// mapnik
|
||||||
|
#include <mapnik/debug.hpp>
|
||||||
|
#include <mapnik/image_reader.hpp>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include <webp/types.h>
|
||||||
|
#include <webp/decode.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
// boost
|
||||||
|
#include <boost/iostreams/device/file.hpp>
|
||||||
|
#include <boost/iostreams/device/array.hpp>
|
||||||
|
#include <boost/iostreams/stream.hpp>
|
||||||
|
// stl
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace mapnik
|
||||||
|
{
|
||||||
|
|
||||||
|
struct external_buffer_policy
|
||||||
|
{
|
||||||
|
external_buffer_policy( uint8_t const* data, std::size_t size)
|
||||||
|
: data_(data),
|
||||||
|
size_(size) {}
|
||||||
|
|
||||||
|
uint8_t const* data() const
|
||||||
|
{
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const
|
||||||
|
{
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t const* data_;
|
||||||
|
std::size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct internal_buffer_policy
|
||||||
|
{
|
||||||
|
internal_buffer_policy(std::size_t size)
|
||||||
|
: data_((size!=0) ? static_cast<uint8_t*>(::operator new(sizeof(uint8_t) * size)) : 0),
|
||||||
|
size_(size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
uint8_t * data() const
|
||||||
|
{
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const
|
||||||
|
{
|
||||||
|
return size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
~internal_buffer_policy()
|
||||||
|
{
|
||||||
|
::operator delete(data_), data_=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t * data_;
|
||||||
|
std::size_t size_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class webp_reader : public image_reader
|
||||||
|
{
|
||||||
|
typedef T buffer_policy_type;
|
||||||
|
private:
|
||||||
|
struct config_guard
|
||||||
|
{
|
||||||
|
config_guard(WebPDecoderConfig & config)
|
||||||
|
: config_(config) {}
|
||||||
|
|
||||||
|
~config_guard()
|
||||||
|
{
|
||||||
|
WebPFreeDecBuffer(&config_.output);
|
||||||
|
}
|
||||||
|
WebPDecoderConfig & config_;
|
||||||
|
};
|
||||||
|
std::auto_ptr<buffer_policy_type> buffer_;
|
||||||
|
size_t size_;
|
||||||
|
unsigned width_;
|
||||||
|
unsigned height_;
|
||||||
|
public:
|
||||||
|
explicit webp_reader(char const* data, std::size_t size);
|
||||||
|
explicit webp_reader(std::string const& filename);
|
||||||
|
~webp_reader();
|
||||||
|
unsigned width() const;
|
||||||
|
unsigned height() const;
|
||||||
|
bool premultiplied_alpha() const { return false; }
|
||||||
|
void read(unsigned x,unsigned y,image_data_32& image);
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
image_reader* create_webp_reader(char const * data, std::size_t size)
|
||||||
|
{
|
||||||
|
return new webp_reader<external_buffer_policy>(data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
image_reader* create_webp_reader2(std::string const& filename)
|
||||||
|
{
|
||||||
|
return new webp_reader<internal_buffer_policy>(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const bool registered = register_image_reader("webp", create_webp_reader);
|
||||||
|
const bool registered2 = register_image_reader("webp", create_webp_reader2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ctor
|
||||||
|
template <typename T>
|
||||||
|
webp_reader<T>::webp_reader(char const* data, std::size_t size)
|
||||||
|
: buffer_(new buffer_policy_type(reinterpret_cast<uint8_t const*>(data), size)),
|
||||||
|
width_(0),
|
||||||
|
height_(0)
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
webp_reader<T>::webp_reader(std::string const& filename)
|
||||||
|
: buffer_(),
|
||||||
|
size_(0),
|
||||||
|
width_(0),
|
||||||
|
height_(0)
|
||||||
|
{
|
||||||
|
std::ifstream file(filename.c_str(), std::ios::binary);
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
throw image_reader_exception("WEBP: Can't read file:" + filename);
|
||||||
|
}
|
||||||
|
std::streampos beg = file.tellg();
|
||||||
|
file.seekg (0, std::ios::end);
|
||||||
|
std::streampos end = file.tellg();
|
||||||
|
std::size_t file_size = end - beg;
|
||||||
|
file.seekg (0, std::ios::beg);
|
||||||
|
buffer_ = std::auto_ptr<buffer_policy_type>(new buffer_policy_type(file_size));
|
||||||
|
file.read(reinterpret_cast<char*>(buffer_->data()), buffer_->size());
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
throw image_reader_exception("WEBP: Failed to read:" + filename);
|
||||||
|
}
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// dtor
|
||||||
|
template <typename T>
|
||||||
|
webp_reader<T>::~webp_reader()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void webp_reader<T>::init()
|
||||||
|
{
|
||||||
|
int width, height;
|
||||||
|
if (!WebPGetInfo(buffer_->data(), buffer_->size(), &width, &height))
|
||||||
|
{
|
||||||
|
throw image_reader_exception("WEBP reader: WebPGetInfo failed");
|
||||||
|
}
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
unsigned webp_reader<T>::width() const
|
||||||
|
{
|
||||||
|
return width_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
unsigned webp_reader<T>::height() const
|
||||||
|
{
|
||||||
|
return height_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void webp_reader<T>::read(unsigned x0, unsigned y0,image_data_32& image)
|
||||||
|
{
|
||||||
|
WebPDecoderConfig config;
|
||||||
|
config_guard guard(config);
|
||||||
|
if (!WebPInitDecoderConfig(&config))
|
||||||
|
{
|
||||||
|
throw image_reader_exception("WEBP reader: WebPInitDecoderConfig failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
config.options.use_cropping = 1;
|
||||||
|
config.options.crop_left = x0;
|
||||||
|
config.options.crop_top = y0;
|
||||||
|
config.options.crop_width = std::min(width_ - x0, image.width());
|
||||||
|
config.options.crop_height = std::min(height_ - y0, image.height());
|
||||||
|
|
||||||
|
if (WebPGetFeatures(buffer_->data(), buffer_->size(), &config.input) != VP8_STATUS_OK)
|
||||||
|
{
|
||||||
|
throw image_reader_exception("WEBP reader: WebPGetFeatures failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
config.output.colorspace = MODE_RGBA;
|
||||||
|
config.output.u.RGBA.rgba = (uint8_t *)image.getBytes();
|
||||||
|
config.output.u.RGBA.stride = 4 * image.width();
|
||||||
|
config.output.u.RGBA.size = image.width() * image.height() * 4;
|
||||||
|
config.output.is_external_memory = 1;
|
||||||
|
if (WebPDecode(buffer_->data(), buffer_->size(), &config) != VP8_STATUS_OK)
|
||||||
|
{
|
||||||
|
throw image_reader_exception("WEBP reader: WebPDecode failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
tests/cpp_tests/data/blank.webp
Normal file
0
tests/cpp_tests/data/blank.webp
Normal file
|
@ -67,6 +67,20 @@ int main(int argc, char** argv)
|
||||||
BOOST_TEST( true );
|
BOOST_TEST( true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
should_throw = "./tests/cpp_tests/data/blank.webp";
|
||||||
|
BOOST_TEST( mapnik::util::exists( should_throw ) );
|
||||||
|
type = mapnik::type_from_filename(should_throw);
|
||||||
|
BOOST_TEST( type );
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::auto_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(should_throw,*type));
|
||||||
|
BOOST_TEST( false );
|
||||||
|
}
|
||||||
|
catch (std::exception const&)
|
||||||
|
{
|
||||||
|
BOOST_TEST( true );
|
||||||
|
}
|
||||||
|
|
||||||
should_throw = "./tests/data/images/xcode-CgBI.png";
|
should_throw = "./tests/data/images/xcode-CgBI.png";
|
||||||
BOOST_TEST( mapnik::util::exists( should_throw ) );
|
BOOST_TEST( mapnik::util::exists( should_throw ) );
|
||||||
type = mapnik::type_from_filename(should_throw);
|
type = mapnik::type_from_filename(should_throw);
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 240 B |
BIN
tests/python_tests/images/support/encoding-opts/blank-webp.webp
Normal file
BIN
tests/python_tests/images/support/encoding-opts/blank-webp.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 240 B |
Binary file not shown.
After Width: | Height: | Size: 240 B |
BIN
tests/python_tests/images/support/encoding-opts/solid-webp.webp
Normal file
BIN
tests/python_tests/images/support/encoding-opts/solid-webp.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 240 B |
BIN
tests/python_tests/images/support/transparency/white0.webp
Normal file
BIN
tests/python_tests/images/support/transparency/white0.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 358 B |
98
tests/python_tests/webp_encoding_test.py
Normal file
98
tests/python_tests/webp_encoding_test.py
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os, mapnik
|
||||||
|
from timeit import Timer, time
|
||||||
|
from nose.tools import *
|
||||||
|
from utilities import execution_path, run_all
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
# All of the paths used are relative, if we run the tests
|
||||||
|
# from another directory we need to chdir()
|
||||||
|
os.chdir(execution_path('.'))
|
||||||
|
|
||||||
|
tmp_dir = '/tmp/mapnik-webp/'
|
||||||
|
if not os.path.exists(tmp_dir):
|
||||||
|
os.makedirs(tmp_dir)
|
||||||
|
|
||||||
|
opts = [
|
||||||
|
'webp',
|
||||||
|
'webp:q=64',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def gen_filepath(name,format):
|
||||||
|
return os.path.join('images/support/encoding-opts',name+'-'+format.replace(":","+")+'.webp')
|
||||||
|
|
||||||
|
def test_quality_threshold():
|
||||||
|
im = mapnik.Image(256,256)
|
||||||
|
im.tostring('webp:quality=99.99000')
|
||||||
|
im.tostring('webp:quality=0')
|
||||||
|
im.tostring('webp:quality=0.001')
|
||||||
|
|
||||||
|
@raises(RuntimeError)
|
||||||
|
def test_quality_threshold_invalid():
|
||||||
|
im = mapnik.Image(256,256)
|
||||||
|
im.tostring('webp:quality=101')
|
||||||
|
|
||||||
|
@raises(RuntimeError)
|
||||||
|
def test_quality_threshold_invalid2():
|
||||||
|
im = mapnik.Image(256,256)
|
||||||
|
im.tostring('webp:quality=-1')
|
||||||
|
|
||||||
|
generate = False
|
||||||
|
|
||||||
|
def test_expected_encodings():
|
||||||
|
im = mapnik.Image(256,256)
|
||||||
|
for opt in opts:
|
||||||
|
expected = gen_filepath('solid',opt)
|
||||||
|
actual = os.path.join(tmp_dir,os.path.basename(expected))
|
||||||
|
if generate or not os.path.exists(expected):
|
||||||
|
print 'generating expected image %s' % expected
|
||||||
|
im.save(expected,opt)
|
||||||
|
im.save(actual,opt)
|
||||||
|
eq_(mapnik.Image.open(actual).tostring(),
|
||||||
|
mapnik.Image.open(expected).tostring(),
|
||||||
|
'%s (actual) not == to %s (expected)' % (actual,expected))
|
||||||
|
|
||||||
|
for opt in opts:
|
||||||
|
expected = gen_filepath('blank',opt)
|
||||||
|
actual = os.path.join(tmp_dir,os.path.basename(expected))
|
||||||
|
if generate or not os.path.exists(expected):
|
||||||
|
print 'generating expected image %s' % expected
|
||||||
|
im.save(expected,opt)
|
||||||
|
im.save(actual,opt)
|
||||||
|
eq_(mapnik.Image.open(actual).tostring(),
|
||||||
|
mapnik.Image.open(expected).tostring(),
|
||||||
|
'%s (actual) not == to %s (expected)' % (actual,expected))
|
||||||
|
|
||||||
|
def test_transparency_levels():
|
||||||
|
# create partial transparency image
|
||||||
|
im = mapnik.Image(256,256)
|
||||||
|
im.background = mapnik.Color('rgba(255,255,255,.5)')
|
||||||
|
c2 = mapnik.Color('rgba(255,255,0,.2)')
|
||||||
|
c3 = mapnik.Color('rgb(0,255,255)')
|
||||||
|
for y in range(0,im.height()/2):
|
||||||
|
for x in range(0,im.width()/2):
|
||||||
|
im.set_pixel(x,y,c2)
|
||||||
|
for y in range(im.height()/2,im.height()):
|
||||||
|
for x in range(im.width()/2,im.width()):
|
||||||
|
im.set_pixel(x,y,c3)
|
||||||
|
|
||||||
|
t0 = tmp_dir + 'white0.webp'
|
||||||
|
|
||||||
|
# octree
|
||||||
|
format = 'webp'
|
||||||
|
expected = 'images/support/transparency/white0.webp'
|
||||||
|
if generate or not os.path.exists(expected):
|
||||||
|
im.save('images/support/transparency/white0.webp')
|
||||||
|
im.save(t0,format)
|
||||||
|
im_in = mapnik.Image.open(t0)
|
||||||
|
t0_len = len(im_in.tostring(format))
|
||||||
|
eq_(t0_len,len(mapnik.Image.open(expected).tostring(format)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
setup()
|
||||||
|
run_all(eval(x) for x in dir() if x.startswith("test_"))
|
Loading…
Reference in a new issue