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:
Daniel Patterson 2015-05-14 12:18:06 -07:00
parent c3dfda4977
commit 01380588ba
5 changed files with 105 additions and 9 deletions

View file

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

View file

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

View file

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

View file

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

View file

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