Experimenting with libimagequant for mapnik/mapnik#2706
Still TODO: 1 & 4 bit encodings proper Mason integration some error checking adjustable dithering settings??
This commit is contained in:
parent
c3dfda4977
commit
01380588ba
5 changed files with 105 additions and 9 deletions
13
SConstruct
13
SConstruct
|
@ -84,6 +84,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',
|
||||||
|
'imgquant':'ImageQuant C library | configure with IQ_LIBS & IQ_INCLUDES',
|
||||||
'webp':'WEBP C library | configure with WEBP_LIBS & WEBP_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/',
|
||||||
'harfbuzz':'HarfBuzz text shaping library | configure with HB_LIBS & HB_INCLUDES',
|
'harfbuzz':'HarfBuzz text shaping library | configure with HB_LIBS & HB_INCLUDES',
|
||||||
|
@ -346,6 +347,9 @@ opts.AddVariables(
|
||||||
BoolVariable('PNG', 'Build Mapnik with PNG read and write support', 'True'),
|
BoolVariable('PNG', 'Build Mapnik with PNG read and write support', 'True'),
|
||||||
PathVariable('PNG_INCLUDES', 'Search path for libpng include files', '/usr/include', PathVariable.PathAccept),
|
PathVariable('PNG_INCLUDES', 'Search path for libpng include files', '/usr/include', PathVariable.PathAccept),
|
||||||
PathVariable('PNG_LIBS','Search path for libpng library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
PathVariable('PNG_LIBS','Search path for libpng library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
||||||
|
BoolVariable('IQ', 'Build Mapnik with ImageQuant support', 'True'),
|
||||||
|
PathVariable('IQ_INCLUDES', 'Search path for libimagequant include files', '/usr/include', PathVariable.PathAccept),
|
||||||
|
PathVariable('IQ_LIBS','Search path for libimagequant library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
||||||
BoolVariable('JPEG', 'Build Mapnik with JPEG read and write support', 'True'),
|
BoolVariable('JPEG', 'Build Mapnik with JPEG read and write support', 'True'),
|
||||||
PathVariable('JPEG_INCLUDES', 'Search path for libjpeg include files', '/usr/include', PathVariable.PathAccept),
|
PathVariable('JPEG_INCLUDES', 'Search path for libjpeg include files', '/usr/include', PathVariable.PathAccept),
|
||||||
PathVariable('JPEG_LIBS', 'Search path for libjpeg library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
PathVariable('JPEG_LIBS', 'Search path for libjpeg library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
|
||||||
|
@ -1304,6 +1308,15 @@ if not preconfigured:
|
||||||
else:
|
else:
|
||||||
env['SKIPPED_DEPS'].extend(['png'])
|
env['SKIPPED_DEPS'].extend(['png'])
|
||||||
|
|
||||||
|
if env['IQ']:
|
||||||
|
OPTIONAL_LIBSHEADERS.append(['imagequant', 'libimagequant.h', False,'C','-DHAVE_IQ'])
|
||||||
|
inc_path = env['%s_INCLUDES' % 'IQ']
|
||||||
|
lib_path = env['%s_LIBS' % 'IQ']
|
||||||
|
env.AppendUnique(CPPPATH = fix_path(inc_path))
|
||||||
|
env.AppendUnique(LIBPATH = fix_path(lib_path))
|
||||||
|
else:
|
||||||
|
env['SKIPPED_DEPS'].extend(['iq'])
|
||||||
|
|
||||||
if env['WEBP']:
|
if env['WEBP']:
|
||||||
OPTIONAL_LIBSHEADERS.append(['webp', 'webp/decode.h', False,'C','-DHAVE_WEBP'])
|
OPTIONAL_LIBSHEADERS.append(['webp', 'webp/decode.h', False,'C','-DHAVE_WEBP'])
|
||||||
inc_path = env['%s_INCLUDES' % 'WEBP']
|
inc_path = env['%s_INCLUDES' % 'WEBP']
|
||||||
|
|
|
@ -107,6 +107,8 @@ CAIRO_INCLUDES = '${MASON_LINKED_REL}/include'
|
||||||
CAIRO_LIBS = '${MASON_LINKED_REL}/lib'
|
CAIRO_LIBS = '${MASON_LINKED_REL}/lib'
|
||||||
SQLITE_INCLUDES = '${MASON_LINKED_REL}/include'
|
SQLITE_INCLUDES = '${MASON_LINKED_REL}/include'
|
||||||
SQLITE_LIBS = '${MASON_LINKED_REL}/lib'
|
SQLITE_LIBS = '${MASON_LINKED_REL}/lib'
|
||||||
|
IQ_INCLUDES = /usr/local/include
|
||||||
|
IQ_LIBS = /usr/local/lib
|
||||||
BENCHMARK = True
|
BENCHMARK = True
|
||||||
CPP_TESTS = True
|
CPP_TESTS = True
|
||||||
PGSQL2SQLITE = True
|
PGSQL2SQLITE = True
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
// zlib
|
// zlib
|
||||||
#include <zlib.h> // for Z_DEFAULT_COMPRESSION
|
#include <zlib.h> // for Z_DEFAULT_COMPRESSION
|
||||||
|
#include "memory_datasource.hpp"
|
||||||
|
#include "palette.hpp"
|
||||||
|
|
||||||
// boost
|
// boost
|
||||||
|
|
||||||
|
@ -39,6 +41,8 @@
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
#include <png.h>
|
#include <png.h>
|
||||||
|
// TODO: make this optional
|
||||||
|
#include "libimagequant.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_OCTREE_LEVELS 4
|
#define MAX_OCTREE_LEVELS 4
|
||||||
|
@ -46,22 +50,27 @@ extern "C"
|
||||||
namespace mapnik {
|
namespace mapnik {
|
||||||
|
|
||||||
struct png_options {
|
struct png_options {
|
||||||
|
|
||||||
|
enum quantization_type { HEXTREE = 0, OCTTREE = 1, IMGQUANT = 2 };
|
||||||
|
|
||||||
int colors;
|
int colors;
|
||||||
int compression;
|
int compression;
|
||||||
int strategy;
|
int strategy;
|
||||||
int trans_mode;
|
int trans_mode;
|
||||||
|
int iq_speed;
|
||||||
double gamma;
|
double gamma;
|
||||||
bool paletted;
|
bool paletted;
|
||||||
bool use_hextree;
|
quantization_type quantization;
|
||||||
bool use_miniz;
|
bool use_miniz;
|
||||||
png_options() :
|
png_options() :
|
||||||
colors(256),
|
colors(256),
|
||||||
compression(Z_DEFAULT_COMPRESSION),
|
compression(Z_DEFAULT_COMPRESSION),
|
||||||
strategy(Z_DEFAULT_STRATEGY),
|
strategy(Z_DEFAULT_STRATEGY),
|
||||||
trans_mode(-1),
|
trans_mode(-1),
|
||||||
|
iq_speed(3),
|
||||||
gamma(-1),
|
gamma(-1),
|
||||||
paletted(true),
|
paletted(true),
|
||||||
use_hextree(true),
|
quantization(HEXTREE),
|
||||||
use_miniz(false) {}
|
use_miniz(false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -704,6 +713,55 @@ void save_as_png8_pal(T1 & file,
|
||||||
save_as_png8<T1, T2, rgba_palette>(file, image, pal, pal.palette(), pal.alphaTable(), opts);
|
save_as_png8<T1, T2, rgba_palette>(file, image, pal, pal.palette(), pal.alphaTable(), opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This only works with rgba8_t image types
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
void save_as_png8_libimagequant(T1 & file,
|
||||||
|
T2 const& image,
|
||||||
|
png_options const& opts)
|
||||||
|
{
|
||||||
|
unsigned width = image.width();
|
||||||
|
unsigned height = image.height();
|
||||||
|
|
||||||
|
// TODO: can this be done as a single blob, rather than using rows?
|
||||||
|
uint32_t *buf[height];
|
||||||
|
|
||||||
|
// TODO: this won't work on big-endian architectures, liq expects
|
||||||
|
// data in RGBA byte order
|
||||||
|
for (size_t y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
buf[y] = (uint32_t *)image.get_row(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
liq_attr *attr = liq_attr_create();
|
||||||
|
// TODO: set gamma
|
||||||
|
// TODO: error checking
|
||||||
|
liq_set_speed(attr, opts.iq_speed);
|
||||||
|
liq_image *liq_image = liq_image_create_rgba_rows(attr, (void **)buf, width, height, 0);
|
||||||
|
liq_result *res = liq_quantize_image(attr, liq_image);
|
||||||
|
|
||||||
|
// Store palettized version
|
||||||
|
image_gray8 reduced_image(width, height);
|
||||||
|
liq_write_remapped_image(res, liq_image, (void *)reduced_image.data(), width*height*sizeof(gray8_t));
|
||||||
|
const liq_palette *liq_pal = liq_get_palette(res);
|
||||||
|
|
||||||
|
std::vector<mapnik::rgb> palette;
|
||||||
|
std::vector<unsigned> alpha;
|
||||||
|
|
||||||
|
for (int i = 0; i < liq_pal->count; ++i)
|
||||||
|
{
|
||||||
|
palette.push_back({ liq_pal->entries[i].r, liq_pal->entries[i].g, liq_pal->entries[i].b });
|
||||||
|
alpha.push_back(liq_pal->entries[i].a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: support 1 & 4 bit packing, not just 8, to reduce the file size, particularly on blank
|
||||||
|
// or solid tiles
|
||||||
|
save_as_png(file, palette, reduced_image, width, height, 8, alpha, opts);
|
||||||
|
|
||||||
|
liq_attr_destroy(attr);
|
||||||
|
liq_image_destroy(liq_image);
|
||||||
|
liq_result_destroy(res);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // MAPNIK_PNG_IO_HPP
|
#endif // MAPNIK_PNG_IO_HPP
|
||||||
|
|
|
@ -73,6 +73,9 @@ if '-DHAVE_PNG' in env['CPPDEFINES']:
|
||||||
lib_env['LIBS'].append('png')
|
lib_env['LIBS'].append('png')
|
||||||
enabled_imaging_libraries.append('png_reader.cpp')
|
enabled_imaging_libraries.append('png_reader.cpp')
|
||||||
|
|
||||||
|
if '-DHAVE_IQ' in env['CPPDEFINES']:
|
||||||
|
lib_env['LIBS'].append('imagequant')
|
||||||
|
|
||||||
if '-DMAPNIK_USE_PROJ4' in env['CPPDEFINES']:
|
if '-DMAPNIK_USE_PROJ4' in env['CPPDEFINES']:
|
||||||
lib_env['LIBS'].append('proj')
|
lib_env['LIBS'].append('proj')
|
||||||
|
|
||||||
|
|
|
@ -80,11 +80,23 @@ void handle_png_options(std::string const& type,
|
||||||
}
|
}
|
||||||
else if (t == "m=o")
|
else if (t == "m=o")
|
||||||
{
|
{
|
||||||
opts.use_hextree = false;
|
opts.quantization = png_options::OCTTREE;
|
||||||
}
|
}
|
||||||
else if (t == "m=h")
|
else if (t == "m=h")
|
||||||
{
|
{
|
||||||
opts.use_hextree = true;
|
opts.quantization = png_options::HEXTREE;
|
||||||
|
}
|
||||||
|
else if (t == "m=iq")
|
||||||
|
{
|
||||||
|
opts.quantization = png_options::IMGQUANT;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (boost::algorithm::starts_with(t, "iq="))
|
||||||
|
{
|
||||||
|
if (!mapnik::util::string2int(t.substr(3), opts.iq_speed) || opts.iq_speed < 1 || opts.iq_speed > 10)
|
||||||
|
{
|
||||||
|
throw ImageWriterException("invalid iq speed parameter: " + t.substr(3));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (t == "e=miniz")
|
else if (t == "e=miniz")
|
||||||
{
|
{
|
||||||
|
@ -224,14 +236,18 @@ void process_rgba8_png_pal(T const& image,
|
||||||
}
|
}
|
||||||
else if (opts.paletted)
|
else if (opts.paletted)
|
||||||
{
|
{
|
||||||
if (opts.use_hextree)
|
if (opts.quantization == png_options::HEXTREE)
|
||||||
{
|
{
|
||||||
save_as_png8_hex(stream, image, opts);
|
save_as_png8_hex(stream, image, opts);
|
||||||
}
|
}
|
||||||
else
|
else if (opts.quantization == png_options::OCTTREE)
|
||||||
{
|
{
|
||||||
save_as_png8_oct(stream, image, opts);
|
save_as_png8_oct(stream, image, opts);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save_as_png8_libimagequant(stream, image, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -252,14 +268,18 @@ void process_rgba8_png(T const& image,
|
||||||
handle_png_options(t, opts);
|
handle_png_options(t, opts);
|
||||||
if (opts.paletted)
|
if (opts.paletted)
|
||||||
{
|
{
|
||||||
if (opts.use_hextree)
|
if (opts.quantization == png_options::HEXTREE)
|
||||||
{
|
{
|
||||||
save_as_png8_hex(stream, image, opts);
|
save_as_png8_hex(stream, image, opts);
|
||||||
}
|
}
|
||||||
else
|
else if (opts.quantization == png_options::OCTTREE)
|
||||||
{
|
{
|
||||||
save_as_png8_oct(stream, image, opts);
|
save_as_png8_oct(stream, image, opts);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save_as_png8_libimagequant(stream, image, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue