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',
'tiff':'TIFF C library | configure with TIFF_LIBS & TIFF_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',
'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',
@ -346,6 +347,9 @@ opts.AddVariables(
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_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'),
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),
@ -1304,6 +1308,15 @@ if not preconfigured:
else:
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']:
OPTIONAL_LIBSHEADERS.append(['webp', 'webp/decode.h', False,'C','-DHAVE_WEBP'])
inc_path = env['%s_INCLUDES' % 'WEBP']
@ -1962,4 +1975,4 @@ if not HELP_REQUESTED:
SConscript('utils/mapnik-config/build.py')
# write the viewer.ini file
SConscript('demo/viewer/build.py')
SConscript('demo/viewer/build.py')

View file

@ -107,6 +107,8 @@ CAIRO_INCLUDES = '${MASON_LINKED_REL}/include'
CAIRO_LIBS = '${MASON_LINKED_REL}/lib'
SQLITE_INCLUDES = '${MASON_LINKED_REL}/include'
SQLITE_LIBS = '${MASON_LINKED_REL}/lib'
IQ_INCLUDES = /usr/local/include
IQ_LIBS = /usr/local/lib
BENCHMARK = True
CPP_TESTS = True
PGSQL2SQLITE = True

View file

@ -32,6 +32,8 @@
// zlib
#include <zlib.h> // for Z_DEFAULT_COMPRESSION
#include "memory_datasource.hpp"
#include "palette.hpp"
// boost
@ -39,6 +41,8 @@
extern "C"
{
#include <png.h>
// TODO: make this optional
#include "libimagequant.h"
}
#define MAX_OCTREE_LEVELS 4
@ -46,22 +50,27 @@ extern "C"
namespace mapnik {
struct png_options {
enum quantization_type { HEXTREE = 0, OCTTREE = 1, IMGQUANT = 2 };
int colors;
int compression;
int strategy;
int trans_mode;
int iq_speed;
double gamma;
bool paletted;
bool use_hextree;
quantization_type quantization;
bool use_miniz;
png_options() :
colors(256),
compression(Z_DEFAULT_COMPRESSION),
strategy(Z_DEFAULT_STRATEGY),
trans_mode(-1),
iq_speed(3),
gamma(-1),
paletted(true),
use_hextree(true),
quantization(HEXTREE),
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);
}
// 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

View file

@ -73,6 +73,9 @@ if '-DHAVE_PNG' in env['CPPDEFINES']:
lib_env['LIBS'].append('png')
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']:
lib_env['LIBS'].append('proj')

View file

@ -80,11 +80,23 @@ void handle_png_options(std::string const& type,
}
else if (t == "m=o")
{
opts.use_hextree = false;
opts.quantization = png_options::OCTTREE;
}
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")
{
@ -224,14 +236,18 @@ void process_rgba8_png_pal(T const& image,
}
else if (opts.paletted)
{
if (opts.use_hextree)
if (opts.quantization == png_options::HEXTREE)
{
save_as_png8_hex(stream, image, opts);
}
else
else if (opts.quantization == png_options::OCTTREE)
{
save_as_png8_oct(stream, image, opts);
}
else
{
save_as_png8_libimagequant(stream, image, opts);
}
}
else
{
@ -252,14 +268,18 @@ void process_rgba8_png(T const& image,
handle_png_options(t, opts);
if (opts.paletted)
{
if (opts.use_hextree)
if (opts.quantization == png_options::HEXTREE)
{
save_as_png8_hex(stream, image, opts);
}
else
else if (opts.quantization == png_options::OCTTREE)
{
save_as_png8_oct(stream, image, opts);
}
else
{
save_as_png8_libimagequant(stream, image, opts);
}
}
else
{