diff --git a/CMakeLists.txt b/CMakeLists.txt index 0411ffc9..0a709a06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,14 @@ else(PNG_FOUND) report_mandatory_not_found(PNG) endif(PNG_FOUND) +find_package(GIF) +if(GIF_FOUND) + include_directories(${GIF_INCLUDE_DIR}) + target_link_libraries(mapcache ${GIF_LIBRARY}) +else(GIF_FOUND) + report_mandatory_not_found(GIF) +endif(GIF_FOUND) + find_package(JPEG) if(JPEG_FOUND) include_directories(${JPEG_INCLUDE_DIR}) @@ -263,6 +271,7 @@ endmacro() message(STATUS "* Configured options for the mapcache library") message(STATUS " * Mandatory components") message(STATUS " * png: ${PNG_LIBRARY}") +message(STATUS " * gif: ${GIF_LIBRARY}") message(STATUS " * jpeg: ${JPEG_LIBRARY}") message(STATUS " * Curl: ${CURL_LIBRARY}") message(STATUS " * Apr: ${APR_LIBRARY}") diff --git a/include/mapcache.h b/include/mapcache.h index bd862ec6..6d70e710 100644 --- a/include/mapcache.h +++ b/include/mapcache.h @@ -86,6 +86,7 @@ typedef struct mapcache_image_format mapcache_image_format; typedef struct mapcache_image_format_mixed mapcache_image_format_mixed; typedef struct mapcache_image_format_png mapcache_image_format_png; typedef struct mapcache_image_format_png_q mapcache_image_format_png_q; +typedef struct mapcache_image_format_gif mapcache_image_format_gif; typedef struct mapcache_image_format_jpeg mapcache_image_format_jpeg; typedef struct mapcache_cfg mapcache_cfg; typedef struct mapcache_tileset mapcache_tileset; @@ -843,7 +844,7 @@ void mapcache_service_dispatch_request(mapcache_context *ctx, /** @{ */ typedef enum { - GC_UNKNOWN, GC_PNG, GC_JPEG + GC_UNKNOWN, GC_PNG, GC_JPEG, GC_GIF } mapcache_image_format_type; typedef enum { @@ -1200,7 +1201,7 @@ struct mapcache_grid_link { mapcache_extent *restricted_extent; mapcache_extent_i *grid_limits; int minz,maxz; - + /** * tiles above this zoom level will not be stored to the cache, but will be * dynamically generated (either by reconstructing from lower level tiles, or @@ -1506,6 +1507,7 @@ struct mapcache_image_format { char *extension; /**< the extension to use when saving a file with this format */ char *mime_type; mapcache_buffer * (*write)(mapcache_context *ctx, mapcache_image *image, mapcache_image_format * format); + mapcache_buffer * (*write_frames)(mapcache_context *ctx, mapcache_image *images, int numimages, mapcache_image_format * format, int delay); /**< pointer to a function that returns a mapcache_buffer containing the given image encoded * in the specified format */ @@ -1591,6 +1593,27 @@ mapcache_image_format* mapcache_imageio_create_png_q_format(apr_pool_t *pool, ch /** @} */ +typedef struct { + unsigned char b,g,r,a; +} rgbaPixel; + +typedef struct { + unsigned char r,g,b; +} rgbPixel; + + +struct mapcache_image_format_gif { + mapcache_image_format format; + mapcache_buffer * (*write_frames)(mapcache_context *ctx, mapcache_image *images, int numimages, mapcache_image_format * format, int delay); + int *animate; +}; +mapcache_image_format* mapcache_imageio_create_gif_format(apr_pool_t *pool, char *name); +mapcache_buffer* _mapcache_imageio_gif_encode(mapcache_context *ctx, mapcache_image *img, mapcache_image_format *format); + + +mapcache_image_format* mapcache_imageio_create_jpeg_format(apr_pool_t *pool, char *name, int quality, + mapcache_photometric photometric); + /**\defgroup imageio_jpg JPEG Image IO * \ingroup imageio */ /** @{ */ @@ -1741,11 +1764,12 @@ apr_array_header_t* mapcache_timedimension_get_entries_for_value(mapcache_contex struct mapcache_timedimension { mapcache_timedimension_assembly_type assembly_type; void (*configuration_parse_xml)(mapcache_context *context, mapcache_timedimension *dim, ezxml_t node); - apr_array_header_t* (*get_entries_for_interval)(mapcache_context *ctx, mapcache_timedimension *dim, mapcache_tileset *tileset, + apr_array_header_t* (*get_entries_for_interval)(mapcache_context *ctx, mapcache_timedimension *dim, mapcache_tileset *tileset, mapcache_grid *grid, mapcache_extent *extent, time_t start, time_t end); apr_array_header_t* (*get_all_entries)(mapcache_context *ctx, mapcache_timedimension *dim, mapcache_tileset *tileset); char *default_value; char *key; /* TIME, hardcoded */ + int delay; }; #ifdef USE_SQLITE diff --git a/include/pam.h b/include/pam.h new file mode 100644 index 00000000..c6b6ce39 --- /dev/null +++ b/include/pam.h @@ -0,0 +1,165 @@ +/****************************************************************************** + * $Id$ + * + * Project: MapServer + * Purpose: MapCache tile caching support file: PNG format + * Author: Thomas Bonfort and the MapServer team. + * + ****************************************************************************** + * Copyright (c) 1996-2011 Regents of the University of Minnesota. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies of this Software or works derived from this Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + *****************************************************************************/ + +#include "mapcache.h" + +int _mapcache_imageio_quantize_image(mapcache_image *rb, + unsigned int *reqcolors, rgbaPixel *palette, + unsigned int *maxval, + rgbaPixel *forced_palette, int num_forced_palette_entries); +int _mapcache_imageio_classify(mapcache_image *rb, unsigned char *pixels, + rgbaPixel *palette, int numPaletteEntries); + +/** \cond DONOTDOCUMENT */ + +/* + * derivations from pngquant and ppmquant + * + ** pngquant.c - quantize the colors in an alphamap down to a specified number + ** + ** Copyright (C) 1989, 1991 by Jef Poskanzer. + ** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by + ** Stefan Schneider. + ** + ** Permission to use, copy, modify, and distribute this software and its + ** documentation for any purpose and without fee is hereby granted, provided + ** that the above copyright notice appear in all copies and that both that + ** copyright notice and this permission notice appear in supporting + ** documentation. This software is provided "as is" without express or + ** implied warranty. + */ +/* +typedef struct { + unsigned char b,g,r,a; +} rgbaPixel; + +typedef struct { + unsigned char r,g,b; +} rgbPixel; +*/ + + +#define PAM_GETR(p) ((p).r) +#define PAM_GETG(p) ((p).g) +#define PAM_GETB(p) ((p).b) +#define PAM_GETA(p) ((p).a) +#define PAM_ASSIGN(p,red,grn,blu,alf) \ + do { (p).r = (red); (p).g = (grn); (p).b = (blu); (p).a = (alf); } while (0) +#define PAM_EQUAL(p,q) \ + ((p).r == (q).r && (p).g == (q).g && (p).b == (q).b && (p).a == (q).a) +#define PAM_DEPTH(newp,p,oldmaxval,newmaxval) \ + PAM_ASSIGN( (newp), \ + ( (int) PAM_GETR(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ + ( (int) PAM_GETG(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ + ( (int) PAM_GETB(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ + ( (int) PAM_GETA(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval) ) + + +/* from pamcmap.h */ + +typedef struct acolorhist_item *acolorhist_vector; +struct acolorhist_item { + rgbaPixel acolor; + int value; +}; + +typedef struct acolorhist_list_item *acolorhist_list; +struct acolorhist_list_item { + struct acolorhist_item ch; + acolorhist_list next; +}; + +typedef acolorhist_list *acolorhash_table; + +#define MAXCOLORS 32767 + +#define LARGE_NORM +#define REP_AVERAGE_PIXELS + +typedef struct box *box_vector; +struct box { + int ind; + int colors; + int sum; +}; + +acolorhist_vector mediancut(acolorhist_vector achv, int colors, int sum, unsigned char maxval, int newcolors); +int redcompare (const void *ch1, const void *ch2); +int greencompare (const void *ch1, const void *ch2); +int bluecompare (const void *ch1, const void *ch2); +int alphacompare (const void *ch1, const void *ch2); +int sumcompare (const void *b1, const void *b2); + +acolorhist_vector pam_acolorhashtoacolorhist +(acolorhash_table acht, int maxacolors); +acolorhist_vector pam_computeacolorhist +(rgbaPixel **apixels, int cols, int rows, int maxacolors, int* acolorsP); +acolorhash_table pam_computeacolorhash +(rgbaPixel** apixels, int cols, int rows, int maxacolors, int* acolorsP); +acolorhash_table pam_allocacolorhash (void); +int pam_addtoacolorhash +(acolorhash_table acht, rgbaPixel *acolorP, int value); +int pam_lookupacolor (acolorhash_table acht, rgbaPixel* acolorP); +void pam_freeacolorhist (acolorhist_vector achv); +void pam_freeacolorhash (acolorhash_table acht); + + + +/*===========================================================================*/ + + +/* libpam3.c - pam (portable alpha map) utility library part 3 + ** + ** Colormap routines. + ** + ** Copyright (C) 1989, 1991 by Jef Poskanzer. + ** Copyright (C) 1997 by Greg Roelofs. + ** + ** Permission to use, copy, modify, and distribute this software and its + ** documentation for any purpose and without fee is hereby granted, provided + ** that the above copyright notice appear in all copies and that both that + ** copyright notice and this permission notice appear in supporting + ** documentation. This software is provided "as is" without express or + ** implied warranty. + */ + +/* +#include "pam.h" +#include "pamcmap.h" + */ + +#define HASH_SIZE 20023 + +#define pam_hashapixel(p) ( ( ( (long) PAM_GETR(p) * 33023 + \ + (long) PAM_GETG(p) * 30013 + \ + (long) PAM_GETB(p) * 27011 + \ + (long) PAM_GETA(p) * 24007 ) \ + & 0x7fffffff ) % HASH_SIZE ) + +/** \endcond DONOTDOCUMENT */ diff --git a/lib/configuration.c b/lib/configuration.c index 30301b27..a69e195a 100644 --- a/lib/configuration.c +++ b/lib/configuration.c @@ -169,6 +169,9 @@ mapcache_cfg* mapcache_configuration_create(apr_pool_t *pool) mapcache_configuration_add_image_format(cfg, mapcache_imageio_create_png_q_format(pool,"PNG8",MAPCACHE_COMPRESSION_FAST,256), "PNG8"); + mapcache_configuration_add_image_format(cfg, + mapcache_imageio_create_gif_format(pool,"GIF"), + "GIF"); mapcache_configuration_add_image_format(cfg, mapcache_imageio_create_jpeg_format(pool,"JPEG",90,MAPCACHE_PHOTOMETRIC_YCBCR), "JPEG"); diff --git a/lib/configuration_xml.c b/lib/configuration_xml.c index acaed920..812923f7 100644 --- a/lib/configuration_xml.c +++ b/lib/configuration_xml.c @@ -49,6 +49,7 @@ void parseMetadata(mapcache_context *ctx, ezxml_t node, apr_table_t *metadata) void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *tileset) { const char *attr = NULL; + int delay; if(tileset->timedimension) { ctx->set_error(ctx,400,"tileset \"%s\" can only have a single ", tileset->name); return; @@ -66,7 +67,7 @@ void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *t ctx->set_error(ctx,400,"unknown \"type\" attribute \"%s\" for %s's . Expecting one of (sqlite)",attr,tileset->name); return; } - + } else { ctx->set_error(ctx,400,"missing \"type\" attribute for %s's ",tileset->name); return; @@ -77,7 +78,7 @@ void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *t } else { tileset->timedimension->key = apr_pstrdup(ctx->pool,"TIME"); } - + attr = ezxml_attr(node,"default"); if(attr && *attr) { tileset->timedimension->default_value = apr_pstrdup(ctx->pool,attr); @@ -85,6 +86,25 @@ void parseTimeDimension(mapcache_context *ctx, ezxml_t node, mapcache_tileset *t ctx->set_error(ctx,400,"no \"default\" attribute for %s",tileset->timedimension->key); return; } + + attr = ezxml_attr(node, "animate"); + if (attr && *attr){ + if(!strcasecmp(attr, "true")) { + tileset->timedimension->assembly_type = MAPCACHE_TIMEDIMENSION_ASSEMBLY_ANIMATE; + attr = ezxml_attr(node, "delay"); + if (attr && *attr) { + delay = atoi(attr); + if (delay > 0) + tileset->timedimension->delay = delay; + else + ctx->set_error(ctx,400, "the \"delay \" attribute for %s must be an integer bigger than 0", tileset->timedimension->key); + } + } + } else { + ctx->set_error(ctx,400,"no \"animate\" attribute for %s", tileset->timedimension->key); +} + + tileset->timedimension->configuration_parse_xml(ctx,tileset->timedimension,node); } @@ -701,7 +721,7 @@ void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config) ctx->set_error(ctx, 400, "invalid grid maxzoom/minzoom %d/%d", gridlink->minz,gridlink->maxz); return; } - + sTolerance = (char*)ezxml_attr(cur_node,"max-cached-zoom"); /* RFC97 implementation: check for a maximum zoomlevel to cache */ if(sTolerance) { @@ -712,14 +732,14 @@ void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config) sTolerance); return; } - + if(tolerance > gridlink->maxz) { ctx->set_error(ctx, 400, "failed to parse grid max-cached-zoom %s (max cached zoom is greater than grid's max zoom)", sTolerance); return; } gridlink->max_cached_zoom = tolerance; - + /* default to reassembling */ gridlink->outofzoom_strategy = MAPCACHE_OUTOFZOOM_REASSEMBLE; sTolerance = (char*)ezxml_attr(cur_node,"out-of-zoom-strategy"); @@ -750,7 +770,7 @@ void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config) parseDimensions(ctx, cur_node, tileset); GC_CHECK_ERROR(ctx); } - + if ((cur_node = ezxml_child(node,"timedimension")) != NULL) { parseTimeDimension(ctx, cur_node, tileset); GC_CHECK_ERROR(ctx); diff --git a/lib/core.c b/lib/core.c index 27108e77..ce8c028d 100644 --- a/lib/core.c +++ b/lib/core.c @@ -222,7 +222,7 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r if(tile->expires && (tile->expires < expires || expires == 0)) { expires = tile->expires; } - + if(tile->nodata) { /* treat the special case where the cache explicitely stated that the tile was empty, and we don't have any vertical merging to do */ @@ -233,8 +233,8 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r } continue; } - - /* treat the most common case: + + /* treat the most common case: - we have a single tile request (i.e. isempty is true) - the cache returned the encoded image */ @@ -271,7 +271,7 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r mapcache_image_merge(ctx, base, tile->raw_image); } else { /* we don't need to merge onto an existing tile and don't have access to the tile's encoded data. - * + * * we don't encode the tile's raw image data just yet because we might need to merge another one on top * of it later. */ @@ -307,7 +307,7 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r format = mapcache_configuration_get_image_format(ctx->config,"PNG8"); } } - + /* compute the content-type */ mapcache_image_format_type t = mapcache_imageio_header_sniff(ctx,response->data); if(t == GC_PNG) @@ -329,79 +329,142 @@ mapcache_http_response *mapcache_core_get_tile(mapcache_context *ctx, mapcache_r return response; } -mapcache_map* mapcache_assemble_maps(mapcache_context *ctx, mapcache_map **maps, int nmaps, mapcache_resample_mode mode) +mapcache_map* mapcache_assemble_maps(mapcache_context *ctx, mapcache_map **pmaps, int nmaps, mapcache_resample_mode mode) { mapcache_tile ***maptiles; int *nmaptiles; mapcache_tile **tiles; + mapcache_map **maps = apr_pcalloc(ctx->pool, nmaps*sizeof(mapcache_map*)); + mapcache_image *animatedframes = NULL; mapcache_map *basemap = NULL; int ntiles = 0; - int i; + int i, frame, frameidx, numframes = 0, nummaps = 0, currentframe = 0; maptiles = apr_pcalloc(ctx->pool,nmaps*sizeof(mapcache_tile**)); nmaptiles = apr_pcalloc(ctx->pool,nmaps*sizeof(int)); - for(i=0; itileset,maps[i]->grid_link, - &maps[i]->extent, maps[i]->width, maps[i]->height, - &(nmaptiles[i]), &(maptiles[i])); - ntiles += nmaptiles[i]; - } - tiles = apr_pcalloc(ctx->pool,ntiles * sizeof(mapcache_tile*)); - ntiles = 0; - for(i=0; idimensions = maps[i]->dimensions; - ntiles++; + + /* + * If we need to assemble and animated map, we find out how many frames it has + * and setup the image buffer. + */ + if (pmaps[0]->tileset->timedimension && + pmaps[0]->tileset->timedimension->assembly_type == MAPCACHE_TIMEDIMENSION_ASSEMBLY_ANIMATE) + { + for (i = 0; i < nmaps; ++i) + { + if(apr_table_get(pmaps[i]->dimensions, pmaps[i]->tileset->timedimension->key) != NULL) + numframes++; } + animatedframes = mapcache_image_create_with_data(ctx, pmaps[0]->width * numframes, pmaps[0]->height); + } else { + numframes = 1; } - mapcache_prefetch_tiles(ctx,tiles,ntiles); - if(GC_HAS_ERROR(ctx)) return NULL; - for(i=0; inodata) { - continue; + + /* + * If the map is animated this will assemble the map for each frame + * if it is a non-animated map, this will run only once + */ + for (frame = 0; frame < numframes; ++frame) + { + if (animatedframes) + { + basemap = NULL; + frameidx = 0; + nummaps = 0; + for (i = 0; i < nmaps; ++i) + { + if(apr_table_get(pmaps[i]->dimensions, pmaps[i]->tileset->timedimension->key) != NULL) { + if (frameidx == currentframe) + { + frameidx = 999999; + currentframe++; + } else { + frameidx++; + continue; + } + } + maps[nummaps] = pmaps[i]; + nummaps++; } - hasdata++; - /* update the map modification time if it is older than the tile mtime */ - if(tile->mtime>maps[i]->mtime) { - maps[i]->mtime = tile->mtime; + } + else + { + nummaps = nmaps; + maps = pmaps; } - /* set the map expiration delay to the tile expiration delay, - * either if the map hasn't got an expiration delay yet - * or if the tile expiration is shorter than the map expiration - */ - if(!maps[i]->expires || tile->expiresexpires) { - maps[i]->expires = tile->expires; + for(i=0; itileset,maps[i]->grid_link, + &maps[i]->extent, maps[i]->width, maps[i]->height, + &(nmaptiles[i]), &(maptiles[i])); + ntiles += nmaptiles[i]; + } + tiles = apr_pcalloc(ctx->pool,ntiles * sizeof(mapcache_tile*)); + ntiles = 0; + for(i=0; idimensions = maps[i]->dimensions; + ntiles++; } } - if(hasdata) { - maps[i]->raw_image = mapcache_tileset_assemble_map_tiles(ctx,maps[i]->tileset,maps[i]->grid_link, - &maps[i]->extent, maps[i]->width, maps[i]->height, - nmaptiles[i], maptiles[i], - mode); - if(!basemap) { - basemap = maps[i]; + mapcache_prefetch_tiles(ctx,tiles,ntiles); + if(GC_HAS_ERROR(ctx)) return NULL; + for(i=0; inodata) { + continue; + } + hasdata++; + /* update the map modification time if it is older than the tile mtime */ + if(tile->mtime>maps[i]->mtime) { + maps[i]->mtime = tile->mtime; + } + + /* set the map expiration delay to the tile expiration delay, + * either if the map hasn't got an expiration delay yet + * or if the tile expiration is shorter than the map expiration + */ + if(!maps[i]->expires || tile->expiresexpires) { + maps[i]->expires = tile->expires; + } + } + if(hasdata) { + maps[i]->raw_image = mapcache_tileset_assemble_map_tiles(ctx,maps[i]->tileset,maps[i]->grid_link, + &maps[i]->extent, maps[i]->width, maps[i]->height, + nmaptiles[i], maptiles[i], + mode); + if(!basemap) { + basemap = maps[i]; + } else { + mapcache_image_merge(ctx,basemap->raw_image,maps[i]->raw_image); + if(GC_HAS_ERROR(ctx)) return NULL; + if(maps[i]->mtime > basemap->mtime) basemap->mtime = maps[i]->mtime; + if(!basemap->expires || maps[i]->expiresexpires) basemap->expires = maps[i]->expires; + apr_pool_cleanup_run(ctx->pool, maps[i]->raw_image->data, (void*)free) ; + maps[i]->raw_image = NULL; + } } else { - mapcache_image_merge(ctx,basemap->raw_image,maps[i]->raw_image); - if(GC_HAS_ERROR(ctx)) return NULL; - if(maps[i]->mtime > basemap->mtime) basemap->mtime = maps[i]->mtime; - if(!basemap->expires || maps[i]->expiresexpires) basemap->expires = maps[i]->expires; - apr_pool_cleanup_run(ctx->pool, maps[i]->raw_image->data, (void*)free) ; - maps[i]->raw_image = NULL; + maps[i]->nodata = 1; } - } else { - maps[i]->nodata = 1; } + if(!basemap) { + ctx->set_error(ctx,404, + "no tiles containing image data could be retrieved to create map (not in cache, and/or no source configured)"); + return NULL; + } + + if(basemap->raw_image && animatedframes) + memcpy(animatedframes->data+frame*(animatedframes->w/numframes)*animatedframes->h*4, + basemap->raw_image->data, basemap->raw_image->w*basemap->raw_image->h*4); + if(GC_HAS_ERROR(ctx)) return NULL; + } - if(!basemap) { - ctx->set_error(ctx,404, - "no tiles containing image data could be retrieved to create map (not in cache, and/or no source configured)"); - return NULL; - } + if (animatedframes) + basemap->raw_image = animatedframes; + return basemap; } @@ -469,7 +532,13 @@ mapcache_http_response *mapcache_core_get_map(mapcache_context *ctx, mapcache_re if(basemap->raw_image) { format = req_map->getmap_format; /* always defined, defaults to JPEG */ - response->data = format->write(ctx,basemap->raw_image,format); + if(req_map->maps[0]->tileset->timedimension && req_map->maps[0]->tileset->timedimension->assembly_type == MAPCACHE_TIMEDIMENSION_ASSEMBLY_ANIMATE) + if (format->write_frames) + response->data = format->write_frames(ctx, basemap->raw_image, req_map->nmaps, format, req_map->maps[0]->tileset->timedimension->delay); + else + ctx->set_error(ctx,500,"Asked for animated time dimension with a non-animated format"); + else + response->data = format->write(ctx,basemap->raw_image,format); if(GC_HAS_ERROR(ctx)) { return NULL; } diff --git a/lib/dimension.c b/lib/dimension.c index 6590368f..d370268e 100644 --- a/lib/dimension.c +++ b/lib/dimension.c @@ -319,13 +319,13 @@ struct sqlite_time_conn { static apr_status_t _sqlite_time_reslist_get_ro_connection(void **conn_, void *params, apr_pool_t *pool) { int ret; - int flags; + int flags; mapcache_timedimension_sqlite *dim = (mapcache_timedimension_sqlite*) params; struct sqlite_time_conn *conn = apr_pcalloc(pool, sizeof (struct sqlite_time_conn)); *conn_ = conn; flags = SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX; ret = sqlite3_open_v2(dim->dbfile, &conn->handle, flags, NULL); - + if (ret != SQLITE_OK) { return APR_EGENERAL; } @@ -348,12 +348,12 @@ static struct sqlite_time_conn* _sqlite_time_get_conn(mapcache_context *ctx, map struct sqlite_time_conn *conn = NULL; apr_reslist_t *pool = NULL; if(!time_connection_pools || NULL == (pool = apr_hash_get(time_connection_pools,dim->timedimension.key, APR_HASH_KEY_STRING)) ) { - + #ifdef APR_HAS_THREADS if(ctx->threadlock) apr_thread_mutex_lock((apr_thread_mutex_t*)ctx->threadlock); #endif - + if(!time_connection_pools) { time_connection_pools = apr_hash_make(ctx->process_pool); } @@ -507,14 +507,14 @@ apr_array_header_t *_mapcache_timedimension_sqlite_get_entries(mapcache_context } stmt = conn->prepared_statement; } - + _bind_sqlite_timedimension_params(ctx,stmt,conn->handle,tileset,grid,extent,start,end); if(GC_HAS_ERROR(ctx)) { sqlite3_reset(stmt); _sqlite_time_release_conn(ctx, sdim, conn); return NULL; } - + time_ids = apr_array_make(ctx->pool,0,sizeof(char*)); do { ret = sqlite3_step(stmt); @@ -554,7 +554,7 @@ typedef enum { void _mapcache_timedimension_sqlite_parse_xml(mapcache_context *ctx, mapcache_timedimension *dim, ezxml_t node) { mapcache_timedimension_sqlite *sdim = (mapcache_timedimension_sqlite*)dim; ezxml_t child; - + child = ezxml_child(node,"dbfile"); if(child && child->txt && *child->txt) { sdim->dbfile = apr_pstrdup(ctx->pool,child->txt); @@ -611,7 +611,7 @@ apr_array_header_t* mapcache_timedimension_get_entries_for_value(mapcache_contex ctx->set_error(ctx,400,"failed to parse time %s",value); return NULL; } - + if(*valueptr == '/') { /* we have a second (end) time */ valueptr++; @@ -664,6 +664,7 @@ mapcache_timedimension* mapcache_timedimension_sqlite_create(apr_pool_t *pool) { dim->timedimension.get_entries_for_interval = _mapcache_timedimension_sqlite_get_entries; dim->timedimension.get_all_entries = _mapcache_timedimension_sqlite_get_all_entries; dim->timedimension.configuration_parse_xml = _mapcache_timedimension_sqlite_parse_xml; + dim->timedimension.delay = 100; //default value in hundreths of a second return (mapcache_timedimension*)dim; } #endif diff --git a/lib/imageio_gif.c b/lib/imageio_gif.c new file mode 100644 index 00000000..ce688c37 --- /dev/null +++ b/lib/imageio_gif.c @@ -0,0 +1,266 @@ +/****************************************************************************** + * $Id$ + * + * Project: MapServer + * Purpose: MapCache tile caching support file: GIF format + * Author: Thomas Bonfort and the MapServer team. + * + ****************************************************************************** + * Copyright (c) 1996-2011 Regents of the University of Minnesota. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies of this Software or works derived from this Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + *****************************************************************************/ + + +#include "mapcache.h" +#include +#include +#include "pam.h" + + +void mapcache_imageio_gif_write_data_to_buffer(GifFileType * _gif, unsigned char *_buf, int _len) +{ + mapcache_buffer *buffer = _gif->UserData; + mapcache_buffer_append(buffer, _len, _buf); +} + +/* Simple function to create the Gif ColorMap */ +int mapcache_imageio_gif_create_colorMap(mapcache_image *img, ColorMapObject **retColorMap, unsigned char *pixels, + rgbaPixel *palette, unsigned int *numPaletteEntries) +{ + int i; + unsigned int maxval; + int numEntries; + ColorMapObject *ColorMap = NULL; + + /* Compute the palette, force to 256 */ + if(MAPCACHE_SUCCESS != _mapcache_imageio_quantize_image(img, numPaletteEntries, palette, &maxval, NULL, 0)) + return MAPCACHE_FAILURE; + + + /* Assign the colors to the gif_lib colormap object + * We need to make sure numEntries is a power of two */ + numEntries = 1 << BitSize(*numPaletteEntries); + if ((ColorMap = MakeMapObject(numEntries, NULL)) == NULL) + return MAPCACHE_FAILURE; + + for (i=0; i<*numPaletteEntries; i++) + { + ColorMap->Colors[i].Red = palette[i].r; + ColorMap->Colors[i].Green = palette[i].g; + ColorMap->Colors[i].Blue = palette[i].b; + } + + *retColorMap = ColorMap; + return MAPCACHE_SUCCESS; +} + +int mapcache_imageio_gif_write_to_giffile(GifFileType *GifFile, mapcache_image *img, unsigned char *pixels) +{ + int row; + + /* Dump out the image descriptor: */ + if (EGifPutImageDesc(GifFile, 0, 0, img->w, img->h, 0, NULL) == GIF_ERROR) + return MAPCACHE_FAILURE; + + /* Step 4: Write the gif content, line by line */ + for(row=0; rowh; row++) + { + if (EGifPutLine(GifFile, pixels+row*img->w, img->w) == GIF_ERROR) + return MAPCACHE_FAILURE; + } + return MAPCACHE_SUCCESS; +} + +mapcache_buffer* _mapcache_imageio_gif_encode(mapcache_context *ctx, mapcache_image *img, mapcache_image_format *format) +{ + /* Step 1: Open the file pointer */ + GifFileType *GifFile; + ColorMapObject *ColorMap = NULL; + mapcache_buffer *buffer = NULL; + unsigned int numPaletteEntries = 256; + rgbaPixel palette[256]; + unsigned char *pixels = (unsigned char*)apr_pcalloc(ctx->pool,img->w*img->h*sizeof(unsigned char)); + + buffer = mapcache_buffer_create(5000,ctx->pool); + + GifFile = EGifOpen(buffer, (OutputFunc)mapcache_imageio_gif_write_data_to_buffer); + + if(GifFile == NULL) + { + ctx->set_error(ctx,500,"failed to create GIF image buffer"); + return NULL; + } + + /* Step 2: Create ColorMap */ + if (mapcache_imageio_gif_create_colorMap(img, &ColorMap, pixels, palette, &numPaletteEntries) != MAPCACHE_SUCCESS) + { + ctx->set_error(ctx,500, "Failed to create GIF ColorMap"); + FreeMapObject(ColorMap); + return NULL; + } + + if (MAPCACHE_SUCCESS != _mapcache_imageio_classify(img, pixels, palette, numPaletteEntries)){ + ctx->set_error(ctx,500, "Failed to classify palette"); + FreeMapObject(ColorMap); + return NULL; + } + + /* Step 3: Write the gif header */ + if (EGifPutScreenDesc(GifFile, img->w, img->h, 256, 0, ColorMap) == GIF_ERROR) + { + ctx->set_error(ctx,500,"failed to create GIF screen"); + FreeMapObject(ColorMap); + return NULL; + } + + // if(map->transparent == MS_ON) + { + GifByteType extentionblock[4] = {0x01, 0x00, 0x00, 0x00}; + EGifPutExtension(GifFile, 0xf9, 4, &extentionblock); + } + + if(MAPCACHE_SUCCESS != mapcache_imageio_gif_write_to_giffile(GifFile, img, pixels)) { + ctx->set_error(ctx,500, "Failed to write gif file"); + FreeMapObject(ColorMap); + return NULL; + } + + /* Step 5: Close file and write footer */ + if (EGifCloseFile(GifFile) == GIF_ERROR) + { + ctx->set_error(ctx,500,"failed to close GIF"); + FreeMapObject(ColorMap); + return NULL; + } + return buffer; +} + +mapcache_buffer* _mapcache_imageio_animated_gif_encode(mapcache_context *ctx, mapcache_image *images, int numimages, mapcache_image_format * format, int delay) +{ + /* Step 1: Open the file pointer */ + mapcache_image *img = NULL; + int rwidth = images->w/numimages; // Real width of one image + GifFileType *GifFile; + int i; + ColorMapObject *ColorMap = NULL; + mapcache_buffer *buffer = NULL; + unsigned int numPaletteEntries = 256; + rgbaPixel palette[256]; + unsigned char *pixels = (unsigned char*)apr_pcalloc(ctx->pool,images->w*images->h*sizeof(unsigned char)); + + + buffer = mapcache_buffer_create(5000,ctx->pool); + + GifFile = EGifOpen(buffer, (OutputFunc)mapcache_imageio_gif_write_data_to_buffer); + + if(GifFile == NULL) + { + ctx->set_error(ctx,500,"failed to create GIF image buffer"); + return NULL; + } + + /* Step 2: Create ColorMap */ + if (mapcache_imageio_gif_create_colorMap(images, &ColorMap, pixels, palette, &numPaletteEntries) != MAPCACHE_SUCCESS) + { + ctx->set_error(ctx,500, "Failed to create GIF ColorMap"); + FreeMapObject(ColorMap); + return NULL; + } + + /* Step 3: Write the gif header */ + if (EGifPutScreenDesc(GifFile, rwidth, images->h, 256, 0, ColorMap) == GIF_ERROR) + { + ctx->set_error(ctx,500,"failed to create GIF screen"); + FreeMapObject(ColorMap); + return NULL; + } + + /* first extension block is NETSCAPE2.0*/ + { + char nsle[12] = "NETSCAPE2.0"; + char subblock[3]; + if (EGifPutExtensionFirst(GifFile, APPLICATION_EXT_FUNC_CODE, 11, nsle) == GIF_ERROR) { + ctx->set_error(ctx,500,"failed to create GIF extension block"); + FreeMapObject(ColorMap); + return NULL; + } + /* The following define the loop count. 0 == infinite */ + subblock[0] = 1; + subblock[2] = 0; + subblock[1] = 0; + if (EGifPutExtensionLast(GifFile, APPLICATION_EXT_FUNC_CODE, 3, subblock) == GIF_ERROR) { + ctx->set_error(ctx,500,"failed to create GIF extension block"); + FreeMapObject(ColorMap); + return NULL; + } + } + + for(i=0; ih); + img->data = images->data+i*rwidth*images->h*4; + + if(MAPCACHE_SUCCESS != _mapcache_imageio_classify(img, pixels, palette, numPaletteEntries)) { + ctx->set_error(ctx,500,"failed to quantize image buffer"); + FreeMapObject(ColorMap); + return NULL; + } + + // if(map->transparent == MS_ON) + { + /* The first byte is a bit mask: 1=transparent, 4=stack frames, 8=erase old frames */ + GifByteType extentionblock[4] = {0x09, delay, 0x00, 0x00}; + EGifPutExtension(GifFile, 0xf9, 4, &extentionblock); + } + + if(MAPCACHE_SUCCESS != mapcache_imageio_gif_write_to_giffile(GifFile, img, pixels)) { + ctx->set_error(ctx,500, "Failed to write gif file"); + FreeMapObject(ColorMap); + return NULL; + } + } + + /* Step 5: Close file and write footer */ + if (EGifCloseFile(GifFile) == GIF_ERROR) + { + ctx->set_error(ctx,500,"failed to close GIF"); + return NULL; + } + FreeMapObject(ColorMap); + return buffer; +} + +mapcache_image_format* mapcache_imageio_create_gif_format(apr_pool_t *pool, char *name) +{ + mapcache_image_format_gif *format = apr_pcalloc(pool, sizeof(mapcache_image_format_gif)); + format->format.name = name; + format->format.extension = apr_pstrdup(pool,"gif"); + format->format.mime_type = apr_pstrdup(pool,"image/gif"); + format->format.metadata = apr_table_make(pool,3); + format->format.write = _mapcache_imageio_gif_encode; + format->format.write_frames = _mapcache_imageio_animated_gif_encode; + format->format.type = GC_GIF; + format->animate = NULL; + return (mapcache_image_format*)format; +} + +/** @} */ + +/* vim: ts=2 sts=2 et sw=2 +*/ diff --git a/lib/imageio_png.c b/lib/imageio_png.c index e960ed72..2d72c689 100644 --- a/lib/imageio_png.c +++ b/lib/imageio_png.c @@ -31,6 +31,7 @@ #include "mapcache.h" #include #include +#include "pam.h" #ifdef _WIN32 typedef unsigned char uint8_t; @@ -463,755 +464,6 @@ mapcache_buffer* _mapcache_imageio_png_encode(mapcache_context *ctx, mapcache_im return buffer; } -/** \cond DONOTDOCUMENT */ - -/* - * derivations from pngquant and ppmquant - * - ** pngquant.c - quantize the colors in an alphamap down to a specified number - ** - ** Copyright (C) 1989, 1991 by Jef Poskanzer. - ** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by - ** Stefan Schneider. - ** - ** Permission to use, copy, modify, and distribute this software and its - ** documentation for any purpose and without fee is hereby granted, provided - ** that the above copyright notice appear in all copies and that both that - ** copyright notice and this permission notice appear in supporting - ** documentation. This software is provided "as is" without express or - ** implied warranty. - */ - -typedef struct { - unsigned char b,g,r,a; -} rgbaPixel; - -typedef struct { - unsigned char r,g,b; -} rgbPixel; - -#define PAM_GETR(p) ((p).r) -#define PAM_GETG(p) ((p).g) -#define PAM_GETB(p) ((p).b) -#define PAM_GETA(p) ((p).a) -#define PAM_ASSIGN(p,red,grn,blu,alf) \ - do { (p).r = (red); (p).g = (grn); (p).b = (blu); (p).a = (alf); } while (0) -#define PAM_EQUAL(p,q) \ - ((p).r == (q).r && (p).g == (q).g && (p).b == (q).b && (p).a == (q).a) -#define PAM_DEPTH(newp,p,oldmaxval,newmaxval) \ - PAM_ASSIGN( (newp), \ - ( (int) PAM_GETR(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ - ( (int) PAM_GETG(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ - ( (int) PAM_GETB(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval), \ - ( (int) PAM_GETA(p) * (newmaxval) + (oldmaxval) / 2 ) / (oldmaxval) ) - - -/* from pamcmap.h */ - -typedef struct acolorhist_item *acolorhist_vector; -struct acolorhist_item { - rgbaPixel acolor; - int value; -}; - -typedef struct acolorhist_list_item *acolorhist_list; -struct acolorhist_list_item { - struct acolorhist_item ch; - acolorhist_list next; -}; - -typedef acolorhist_list *acolorhash_table; - -#define MAXCOLORS 32767 - -#define LARGE_NORM -#define REP_AVERAGE_PIXELS - -typedef struct box *box_vector; -struct box { - int ind; - int colors; - int sum; -}; - -static acolorhist_vector mediancut(acolorhist_vector achv, int colors, int sum, unsigned char maxval, int newcolors); -static int redcompare (const void *ch1, const void *ch2); -static int greencompare (const void *ch1, const void *ch2); -static int bluecompare (const void *ch1, const void *ch2); -static int alphacompare (const void *ch1, const void *ch2); -static int sumcompare (const void *b1, const void *b2); - -static acolorhist_vector pam_acolorhashtoacolorhist -(acolorhash_table acht, int maxacolors); -static acolorhist_vector pam_computeacolorhist -(rgbaPixel **apixels, int cols, int rows, int maxacolors, int* acolorsP); -static acolorhash_table pam_computeacolorhash -(rgbaPixel** apixels, int cols, int rows, int maxacolors, int* acolorsP); -static acolorhash_table pam_allocacolorhash (void); -static int pam_addtoacolorhash -(acolorhash_table acht, rgbaPixel *acolorP, int value); -static int pam_lookupacolor (acolorhash_table acht, rgbaPixel* acolorP); -static void pam_freeacolorhist (acolorhist_vector achv); -static void pam_freeacolorhash (acolorhash_table acht); - - -/** - * Compute a palette for the given RGBA rasterBuffer using a median cut quantization. - * - rb: the rasterBuffer to quantize - * - reqcolors: the desired number of colors the palette should contain. will be set - * with the actual number of entries in the computed palette - * - palette: preallocated array of palette entries that will be populated by the - * function - * - maxval: max value of pixel intensity. In some cases, the input data has to - * be rescaled to compute the quantization. if the returned value of maxscale is - * less than 255, this means that the input pixels have been rescaled, and that - * the returned palette must be upscaled before being written to the png file - * - forced_palette: entries that should appear in the computed palette - * - num_forced_palette_entries: number of entries contained in "force_palette". if 0, - * "force_palette" can be NULL - */ -int _mapcache_imageio_quantize_image(mapcache_image *rb, - unsigned int *reqcolors, rgbaPixel *palette, - unsigned int *maxval, - rgbaPixel *forced_palette, int num_forced_palette_entries) -{ - - rgbaPixel **apixels=NULL; /* pointer to the start rows of truecolor pixels */ - register rgbaPixel *pP; - register int col; - - unsigned char newmaxval; - acolorhist_vector achv, acolormap=NULL; - - int row; - int colors; - int newcolors = 0; - - int x; - /* int channels; */ - - - *maxval = 255; - - apixels=(rgbaPixel**)malloc(rb->h*sizeof(rgbaPixel**)); - if(!apixels) return MAPCACHE_FAILURE; - - for(row=0; rowh; row++) { - apixels[row]=(rgbaPixel*)(&(rb->data[row * rb->stride])); - } - - /* - ** Step 2: attempt to make a histogram of the colors, unclustered. - ** If at first we don't succeed, lower maxval to increase color - ** coherence and try again. This will eventually terminate, with - ** maxval at worst 15, since 32^3 is approximately MAXCOLORS. - [GRR POSSIBLE BUG: what about 32^4 ?] - */ - for ( ; ; ) { - achv = pam_computeacolorhist( - apixels, rb->w, rb->h, MAXCOLORS, &colors ); - if ( achv != (acolorhist_vector) 0 ) - break; - newmaxval = *maxval / 2; - for ( row = 0; row < rb->h; ++row ) - for ( col = 0, pP = apixels[row]; col < rb->w; ++col, ++pP ) - PAM_DEPTH( *pP, *pP, *maxval, newmaxval ); - *maxval = newmaxval; - } - newcolors = MAPCACHE_MIN(colors, *reqcolors); - acolormap = mediancut(achv, colors, rb->w*rb->h, *maxval, newcolors); - pam_freeacolorhist(achv); - - - *reqcolors = newcolors; - - - for (x = 0; x < newcolors; ++x) { - palette[x].r = acolormap[x].acolor.r; - palette[x].g = acolormap[x].acolor.g; - palette[x].b = acolormap[x].acolor.b; - palette[x].a = acolormap[x].acolor.a; - } - - free(acolormap); - free(apixels); - return MAPCACHE_SUCCESS; -} - - -int _mapcache_imageio_classify(mapcache_image *rb, unsigned char *pixels, - rgbaPixel *palette, int numPaletteEntries) -{ - register int ind; - unsigned char *outrow,*pQ; - register rgbaPixel *pP; - acolorhash_table acht; - int usehash, row, col; - /* - ** Step 4: map the colors in the image to their closest match in the - ** new colormap, and write 'em out. - */ - acht = pam_allocacolorhash( ); - usehash = 1; - - for ( row = 0; row < rb->h; ++row ) { - outrow = &(pixels[row*rb->w]); - col = 0; - pP = (rgbaPixel*)(&(rb->data[row * rb->stride]));; - pQ = outrow; - do { - /* Check hash table to see if we have already matched this color. */ - ind = pam_lookupacolor( acht, pP ); - if ( ind == -1 ) { - /* No; search acolormap for closest match. */ - register int i, r1, g1, b1, a1, r2, g2, b2, a2; - register long dist, newdist; - - r1 = PAM_GETR( *pP ); - g1 = PAM_GETG( *pP ); - b1 = PAM_GETB( *pP ); - a1 = PAM_GETA( *pP ); - dist = 2000000000; - for ( i = 0; i < numPaletteEntries; ++i ) { - r2 = PAM_GETR( palette[i] ); - g2 = PAM_GETG( palette[i] ); - b2 = PAM_GETB( palette[i] ); - a2 = PAM_GETA( palette[i] ); - /* GRR POSSIBLE BUG */ - newdist = ( r1 - r2 ) * ( r1 - r2 ) + /* may overflow? */ - ( g1 - g2 ) * ( g1 - g2 ) + - ( b1 - b2 ) * ( b1 - b2 ) + - ( a1 - a2 ) * ( a1 - a2 ); - if ( newdist < dist ) { - ind = i; - dist = newdist; - } - } - if ( usehash ) { - if ( pam_addtoacolorhash( acht, pP, ind ) < 0 ) { - usehash = 0; - } - } - } - - /* *pP = acolormap[ind].acolor; */ - *pQ = (unsigned char)ind; - - ++col; - ++pP; - ++pQ; - - } while ( col != rb->w ); - } - pam_freeacolorhash(acht); - - return MAPCACHE_SUCCESS; -} - - - -/* - ** Here is the fun part, the median-cut colormap generator. This is based - ** on Paul Heckbert's paper, "Color Image Quantization for Frame Buffer - ** Display," SIGGRAPH 1982 Proceedings, page 297. - */ - -static acolorhist_vector -mediancut(acolorhist_vector achv, int colors, int sum, unsigned char maxval, int newcolors ) -{ - acolorhist_vector acolormap; - box_vector bv; - register int bi, i; - int boxes; - - bv = (box_vector) malloc( sizeof(struct box) * newcolors ); - acolormap = - (acolorhist_vector) malloc( sizeof(struct acolorhist_item) * newcolors); - if ( bv == (box_vector) 0 || acolormap == (acolorhist_vector) 0 ) { - fprintf( stderr, " out of memory allocating box vector\n" ); - fflush(stderr); - exit(6); - } - for ( i = 0; i < newcolors; ++i ) - PAM_ASSIGN( acolormap[i].acolor, 0, 0, 0, 0 ); - - /* - ** Set up the initial box. - */ - bv[0].ind = 0; - bv[0].colors = colors; - bv[0].sum = sum; - boxes = 1; - - /* - ** Main loop: split boxes until we have enough. - */ - while ( boxes < newcolors ) { - register int indx, clrs; - int sm; - register int minr, maxr, ming, mina, maxg, minb, maxb, maxa, v; - int halfsum, lowersum; - - /* - ** Find the first splittable box. - */ - for ( bi = 0; bi < boxes; ++bi ) - if ( bv[bi].colors >= 2 ) - break; - if ( bi == boxes ) - break; /* ran out of colors! */ - indx = bv[bi].ind; - clrs = bv[bi].colors; - sm = bv[bi].sum; - - /* - ** Go through the box finding the minimum and maximum of each - ** component - the boundaries of the box. - */ - minr = maxr = PAM_GETR( achv[indx].acolor ); - ming = maxg = PAM_GETG( achv[indx].acolor ); - minb = maxb = PAM_GETB( achv[indx].acolor ); - mina = maxa = PAM_GETA( achv[indx].acolor ); - for ( i = 1; i < clrs; ++i ) { - v = PAM_GETR( achv[indx + i].acolor ); - if ( v < minr ) minr = v; - if ( v > maxr ) maxr = v; - v = PAM_GETG( achv[indx + i].acolor ); - if ( v < ming ) ming = v; - if ( v > maxg ) maxg = v; - v = PAM_GETB( achv[indx + i].acolor ); - if ( v < minb ) minb = v; - if ( v > maxb ) maxb = v; - v = PAM_GETA( achv[indx + i].acolor ); - if ( v < mina ) mina = v; - if ( v > maxa ) maxa = v; - } - - /* - ** Find the largest dimension, and sort by that component. I have - ** included two methods for determining the "largest" dimension; - ** first by simply comparing the range in RGB space, and second - ** by transforming into luminosities before the comparison. You - ** can switch which method is used by switching the commenting on - ** the LARGE_ defines at the beginning of this source file. - */ -#ifdef LARGE_NORM - if ( maxa - mina >= maxr - minr && maxa - mina >= maxg - ming && maxa - mina >= maxb - minb ) - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - alphacompare ); - else if ( maxr - minr >= maxg - ming && maxr - minr >= maxb - minb ) - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - redcompare ); - else if ( maxg - ming >= maxb - minb ) - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - greencompare ); - else - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - bluecompare ); -#endif /*LARGE_NORM*/ -#ifdef LARGE_LUM - { - apixel p; - float rl, gl, bl, al; - - PAM_ASSIGN(p, maxr - minr, 0, 0, 0); - rl = PPM_LUMIN(p); - PAM_ASSIGN(p, 0, maxg - ming, 0, 0); - gl = PPM_LUMIN(p); - PAM_ASSIGN(p, 0, 0, maxb - minb, 0); - bl = PPM_LUMIN(p); - - /* - GRR: treat alpha as grayscale and assign (maxa - mina) to each of R, G, B? - assign (maxa - mina)/3 to each? - use alpha-fractional luminosity? (normalized_alpha * lum(r,g,b)) - al = dunno ... - [probably should read Heckbert's paper to decide] - */ - - if ( al >= rl && al >= gl && al >= bl ) - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - alphacompare ); - else if ( rl >= gl && rl >= bl ) - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - redcompare ); - else if ( gl >= bl ) - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - greencompare ); - else - qsort( - (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), - bluecompare ); - } -#endif /*LARGE_LUM*/ - - /* - ** Now find the median based on the counts, so that about half the - ** pixels (not colors, pixels) are in each subdivision. - */ - lowersum = achv[indx].value; - halfsum = sm / 2; - for ( i = 1; i < clrs - 1; ++i ) { - if ( lowersum >= halfsum ) - break; - lowersum += achv[indx + i].value; - } - - /* - ** Split the box, and sort to bring the biggest boxes to the top. - */ - bv[bi].colors = i; - bv[bi].sum = lowersum; - bv[boxes].ind = indx + i; - bv[boxes].colors = clrs - i; - bv[boxes].sum = sm - lowersum; - ++boxes; - qsort( (char*) bv, boxes, sizeof(struct box), sumcompare ); - } - - /* - ** Ok, we've got enough boxes. Now choose a representative color for - ** each box. There are a number of possible ways to make this choice. - ** One would be to choose the center of the box; this ignores any structure - ** within the boxes. Another method would be to average all the colors in - ** the box - this is the method specified in Heckbert's paper. A third - ** method is to average all the pixels in the box. You can switch which - ** method is used by switching the commenting on the REP_ defines at - ** the beginning of this source file. - */ - for ( bi = 0; bi < boxes; ++bi ) { -#ifdef REP_CENTER_BOX - register int indx = bv[bi].ind; - register int clrs = bv[bi].colors; - register int minr, maxr, ming, maxg, minb, maxb, mina, maxa, v; - - minr = maxr = PAM_GETR( achv[indx].acolor ); - ming = maxg = PAM_GETG( achv[indx].acolor ); - minb = maxb = PAM_GETB( achv[indx].acolor ); - mina = maxa = PAM_GETA( achv[indx].acolor ); - for ( i = 1; i < clrs; ++i ) { - v = PAM_GETR( achv[indx + i].acolor ); - minr = min( minr, v ); - maxr = max( maxr, v ); - v = PAM_GETG( achv[indx + i].acolor ); - ming = min( ming, v ); - maxg = max( maxg, v ); - v = PAM_GETB( achv[indx + i].acolor ); - minb = min( minb, v ); - maxb = max( maxb, v ); - v = PAM_GETA( achv[indx + i].acolor ); - mina = min( mina, v ); - maxa = max( maxa, v ); - } - PAM_ASSIGN( - acolormap[bi].acolor, ( minr + maxr ) / 2, ( ming + maxg ) / 2, - ( minb + maxb ) / 2, ( mina + maxa ) / 2 ); -#endif /*REP_CENTER_BOX*/ -#ifdef REP_AVERAGE_COLORS - register int indx = bv[bi].ind; - register int clrs = bv[bi].colors; - register long r = 0, g = 0, b = 0, a = 0; - - for ( i = 0; i < clrs; ++i ) { - r += PAM_GETR( achv[indx + i].acolor ); - g += PAM_GETG( achv[indx + i].acolor ); - b += PAM_GETB( achv[indx + i].acolor ); - a += PAM_GETA( achv[indx + i].acolor ); - } - r = r / clrs; - g = g / clrs; - b = b / clrs; - a = a / clrs; - PAM_ASSIGN( acolormap[bi].acolor, r, g, b, a ); -#endif /*REP_AVERAGE_COLORS*/ -#ifdef REP_AVERAGE_PIXELS - register int indx = bv[bi].ind; - register int clrs = bv[bi].colors; - register long r = 0, g = 0, b = 0, a = 0, sum = 0; - - for ( i = 0; i < clrs; ++i ) { - r += PAM_GETR( achv[indx + i].acolor ) * achv[indx + i].value; - g += PAM_GETG( achv[indx + i].acolor ) * achv[indx + i].value; - b += PAM_GETB( achv[indx + i].acolor ) * achv[indx + i].value; - a += PAM_GETA( achv[indx + i].acolor ) * achv[indx + i].value; - sum += achv[indx + i].value; - } - r = r / sum; - if ( r > maxval ) r = maxval; /* avoid math errors */ - g = g / sum; - if ( g > maxval ) g = maxval; - b = b / sum; - if ( b > maxval ) b = maxval; - a = a / sum; - if ( a > maxval ) a = maxval; - /* GRR 20001228: added casts to quiet warnings; 255 DEPENDENCY */ - PAM_ASSIGN( acolormap[bi].acolor, (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a ); -#endif /*REP_AVERAGE_PIXELS*/ - } - - /* - ** All done. - */ - free(bv); - return acolormap; -} - -static int -redcompare( const void *ch1, const void *ch2 ) -{ - return (int) PAM_GETR( ((acolorhist_vector)ch1)->acolor ) - - (int) PAM_GETR( ((acolorhist_vector)ch2)->acolor ); -} - -static int -greencompare( const void *ch1, const void *ch2 ) -{ - return (int) PAM_GETG( ((acolorhist_vector)ch1)->acolor ) - - (int) PAM_GETG( ((acolorhist_vector)ch2)->acolor ); -} - -static int -bluecompare( const void *ch1, const void *ch2 ) -{ - return (int) PAM_GETB( ((acolorhist_vector)ch1)->acolor ) - - (int) PAM_GETB( ((acolorhist_vector)ch2)->acolor ); -} - -static int -alphacompare( const void *ch1, const void *ch2 ) -{ - return (int) PAM_GETA( ((acolorhist_vector)ch1)->acolor ) - - (int) PAM_GETA( ((acolorhist_vector)ch2)->acolor ); -} - -static int -sumcompare( const void *b1, const void *b2 ) -{ - return ((box_vector)b2)->sum - - ((box_vector)b1)->sum; -} - - -/*===========================================================================*/ - - -/* libpam3.c - pam (portable alpha map) utility library part 3 - ** - ** Colormap routines. - ** - ** Copyright (C) 1989, 1991 by Jef Poskanzer. - ** Copyright (C) 1997 by Greg Roelofs. - ** - ** Permission to use, copy, modify, and distribute this software and its - ** documentation for any purpose and without fee is hereby granted, provided - ** that the above copyright notice appear in all copies and that both that - ** copyright notice and this permission notice appear in supporting - ** documentation. This software is provided "as is" without express or - ** implied warranty. - */ - -/* -#include "pam.h" -#include "pamcmap.h" - */ - -#define HASH_SIZE 20023 - -#define pam_hashapixel(p) ( ( ( (long) PAM_GETR(p) * 33023 + \ - (long) PAM_GETG(p) * 30013 + \ - (long) PAM_GETB(p) * 27011 + \ - (long) PAM_GETA(p) * 24007 ) \ - & 0x7fffffff ) % HASH_SIZE ) - -static acolorhist_vector -pam_computeacolorhist( apixels, cols, rows, maxacolors, acolorsP ) -rgbaPixel** apixels; -int cols, rows, maxacolors; -int* acolorsP; -{ - acolorhash_table acht; - acolorhist_vector achv; - - acht = pam_computeacolorhash( apixels, cols, rows, maxacolors, acolorsP ); - if ( acht == (acolorhash_table) 0 ) - return (acolorhist_vector) 0; - achv = pam_acolorhashtoacolorhist( acht, maxacolors ); - pam_freeacolorhash( acht ); - return achv; -} - - - -static acolorhash_table -pam_computeacolorhash( apixels, cols, rows, maxacolors, acolorsP ) -rgbaPixel** apixels; -int cols, rows, maxacolors; -int* acolorsP; -{ - acolorhash_table acht; - register rgbaPixel* pP; - acolorhist_list achl; - int col, row, hash; - - acht = pam_allocacolorhash( ); - *acolorsP = 0; - - /* Go through the entire image, building a hash table of colors. */ - for ( row = 0; row < rows; ++row ) - for ( col = 0, pP = apixels[row]; col < cols; ++col, ++pP ) { - hash = pam_hashapixel( *pP ); - for ( achl = acht[hash]; achl != (acolorhist_list) 0; achl = achl->next ) - if ( PAM_EQUAL( achl->ch.acolor, *pP ) ) - break; - if ( achl != (acolorhist_list) 0 ) - ++(achl->ch.value); - else { - if ( ++(*acolorsP) > maxacolors ) { - pam_freeacolorhash( acht ); - return (acolorhash_table) 0; - } - achl = (acolorhist_list) malloc( sizeof(struct acolorhist_list_item) ); - if ( achl == 0 ) { - fprintf( stderr, " out of memory computing hash table\n" ); - exit(7); - } - achl->ch.acolor = *pP; - achl->ch.value = 1; - achl->next = acht[hash]; - acht[hash] = achl; - } - } - - return acht; -} - - - -static acolorhash_table -pam_allocacolorhash( ) -{ - acolorhash_table acht; - int i; - - acht = (acolorhash_table) malloc( HASH_SIZE * sizeof(acolorhist_list) ); - if ( acht == 0 ) { - fprintf( stderr, " out of memory allocating hash table\n" ); - exit(8); - } - - for ( i = 0; i < HASH_SIZE; ++i ) - acht[i] = (acolorhist_list) 0; - - return acht; -} - - - -static int -pam_addtoacolorhash( acht, acolorP, value ) -acolorhash_table acht; -rgbaPixel* acolorP; -int value; -{ - register int hash; - register acolorhist_list achl; - - achl = (acolorhist_list) malloc( sizeof(struct acolorhist_list_item) ); - if ( achl == 0 ) - return -1; - hash = pam_hashapixel( *acolorP ); - achl->ch.acolor = *acolorP; - achl->ch.value = value; - achl->next = acht[hash]; - acht[hash] = achl; - return 0; -} - - - -static acolorhist_vector -pam_acolorhashtoacolorhist( acht, maxacolors ) -acolorhash_table acht; -int maxacolors; -{ - acolorhist_vector achv; - acolorhist_list achl; - int i, j; - - /* Now collate the hash table into a simple acolorhist array. */ - achv = (acolorhist_vector) malloc( maxacolors * sizeof(struct acolorhist_item) ); - /* (Leave room for expansion by caller.) */ - if ( achv == (acolorhist_vector) 0 ) { - fprintf( stderr, " out of memory generating histogram\n" ); - exit(9); - } - - /* Loop through the hash table. */ - j = 0; - for ( i = 0; i < HASH_SIZE; ++i ) - for ( achl = acht[i]; achl != (acolorhist_list) 0; achl = achl->next ) { - /* Add the new entry. */ - achv[j] = achl->ch; - ++j; - } - - /* All done. */ - return achv; -} - - - -static int -pam_lookupacolor( acht, acolorP ) -acolorhash_table acht; -rgbaPixel* acolorP; -{ - int hash; - acolorhist_list achl; - - hash = pam_hashapixel( *acolorP ); - for ( achl = acht[hash]; achl != (acolorhist_list) 0; achl = achl->next ) - if ( PAM_EQUAL( achl->ch.acolor, *acolorP ) ) - return achl->ch.value; - - return -1; -} - - - -static void -pam_freeacolorhist( achv ) -acolorhist_vector achv; -{ - free( (char*) achv ); -} - - - -static void -pam_freeacolorhash( acht ) -acolorhash_table acht; -{ - int i; - acolorhist_list achl, achlnext; - - for ( i = 0; i < HASH_SIZE; ++i ) - for ( achl = acht[i]; achl != (acolorhist_list) 0; achl = achlnext ) { - achlnext = achl->next; - free( (char*) achl ); - } - free( (char*) acht ); -} - -/** \endcond DONOTDOCUMENT */ - int _mapcache_imageio_remap_palette(unsigned char *pixels, int npixels, rgbaPixel *palette, int numPaletteEntries, unsigned int maxval, rgbPixel *rgb, unsigned char *a, int *num_a) diff --git a/lib/pam.c b/lib/pam.c new file mode 100644 index 00000000..66c6e673 --- /dev/null +++ b/lib/pam.c @@ -0,0 +1,657 @@ +/** \cond DONOTDOCUMENT */ + +/* + * derivations from pngquant and ppmquant + * + ** pngquant.c - quantize the colors in an alphamap down to a specified number + ** + ** Copyright (C) 1989, 1991 by Jef Poskanzer. + ** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by + ** Stefan Schneider. + ** + ** Permission to use, copy, modify, and distribute this software and its + ** documentation for any purpose and without fee is hereby granted, provided + ** that the above copyright notice appear in all copies and that both that + ** copyright notice and this permission notice appear in supporting + ** documentation. This software is provided "as is" without express or + ** implied warranty. + */ + +/** + * Compute a palette for the given RGBA rasterBuffer using a median cut quantization. + * - rb: the rasterBuffer to quantize + * - reqcolors: the desired number of colors the palette should contain. will be set + * with the actual number of entries in the computed palette + * - palette: preallocated array of palette entries that will be populated by the + * function + * - maxval: max value of pixel intensity. In some cases, the input data has to + * be rescaled to compute the quantization. if the returned value of maxscale is + * less than 255, this means that the input pixels have been rescaled, and that + * the returned palette must be upscaled before being written to the png file + * - forced_palette: entries that should appear in the computed palette + * - num_forced_palette_entries: number of entries contained in "force_palette". if 0, + * "force_palette" can be NULL + */ +#include "pam.h" + +int _mapcache_imageio_quantize_image(mapcache_image *rb, + unsigned int *reqcolors, rgbaPixel *palette, + unsigned int *maxval, + rgbaPixel *forced_palette, int num_forced_palette_entries) +{ + + rgbaPixel **apixels=NULL; /* pointer to the start rows of truecolor pixels */ + register rgbaPixel *pP; + register int col; + + unsigned char newmaxval; + acolorhist_vector achv, acolormap=NULL; + + int row; + int colors; + int newcolors = 0; + + int x; + /* int channels; */ + + + *maxval = 255; + + apixels=(rgbaPixel**)malloc(rb->h*sizeof(rgbaPixel**)); + if(!apixels) return MAPCACHE_FAILURE; + + for(row=0; rowh; row++) { + apixels[row]=(rgbaPixel*)(&(rb->data[row * rb->stride])); + } + + /* + ** Step 2: attempt to make a histogram of the colors, unclustered. + ** If at first we don't succeed, lower maxval to increase color + ** coherence and try again. This will eventually terminate, with + ** maxval at worst 15, since 32^3 is approximately MAXCOLORS. + [GRR POSSIBLE BUG: what about 32^4 ?] + */ + for ( ; ; ) { + achv = pam_computeacolorhist( + apixels, rb->w, rb->h, MAXCOLORS, &colors ); + if ( achv != (acolorhist_vector) 0 ) + break; + newmaxval = *maxval / 2; + for ( row = 0; row < rb->h; ++row ) + for ( col = 0, pP = apixels[row]; col < rb->w; ++col, ++pP ) + PAM_DEPTH( *pP, *pP, *maxval, newmaxval ); + *maxval = newmaxval; + } + newcolors = MAPCACHE_MIN(colors, *reqcolors); + acolormap = mediancut(achv, colors, rb->w*rb->h, *maxval, newcolors); + pam_freeacolorhist(achv); + + + *reqcolors = newcolors; + + + for (x = 0; x < newcolors; ++x) { + palette[x].r = acolormap[x].acolor.r; + palette[x].g = acolormap[x].acolor.g; + palette[x].b = acolormap[x].acolor.b; + palette[x].a = acolormap[x].acolor.a; + } + + free(acolormap); + free(apixels); + return MAPCACHE_SUCCESS; +} + + +int _mapcache_imageio_classify(mapcache_image *rb, unsigned char *pixels, + rgbaPixel *palette, int numPaletteEntries) +{ + register int ind; + unsigned char *outrow,*pQ; + register rgbaPixel *pP; + acolorhash_table acht; + int usehash, row, col; + /* + ** Step 4: map the colors in the image to their closest match in the + ** new colormap, and write 'em out. + */ + acht = pam_allocacolorhash( ); + usehash = 1; + + for ( row = 0; row < rb->h; ++row ) { + outrow = &(pixels[row*rb->w]); + col = 0; + pP = (rgbaPixel*)(&(rb->data[row * rb->stride]));; + pQ = outrow; + do { + /* Check hash table to see if we have already matched this color. */ + ind = pam_lookupacolor( acht, pP ); + if ( ind == -1 ) { + /* No; search acolormap for closest match. */ + register int i, r1, g1, b1, a1, r2, g2, b2, a2; + register long dist, newdist; + + r1 = PAM_GETR( *pP ); + g1 = PAM_GETG( *pP ); + b1 = PAM_GETB( *pP ); + a1 = PAM_GETA( *pP ); + dist = 2000000000; + for ( i = 0; i < numPaletteEntries; ++i ) { + r2 = PAM_GETR( palette[i] ); + g2 = PAM_GETG( palette[i] ); + b2 = PAM_GETB( palette[i] ); + a2 = PAM_GETA( palette[i] ); + /* GRR POSSIBLE BUG */ + newdist = ( r1 - r2 ) * ( r1 - r2 ) + /* may overflow? */ + ( g1 - g2 ) * ( g1 - g2 ) + + ( b1 - b2 ) * ( b1 - b2 ) + + ( a1 - a2 ) * ( a1 - a2 ); + if ( newdist < dist ) { + ind = i; + dist = newdist; + } + } + if ( usehash ) { + if ( pam_addtoacolorhash( acht, pP, ind ) < 0 ) { + usehash = 0; + } + } + } + + /* *pP = acolormap[ind].acolor; */ + *pQ = (unsigned char)ind; + + ++col; + ++pP; + ++pQ; + + } while ( col != rb->w ); + } + pam_freeacolorhash(acht); + + return MAPCACHE_SUCCESS; +} + + + +/* + ** Here is the fun part, the median-cut colormap generator. This is based + ** on Paul Heckbert's paper, "Color Image Quantization for Frame Buffer + ** Display," SIGGRAPH 1982 Proceedings, page 297. + */ + +acolorhist_vector +mediancut(acolorhist_vector achv, int colors, int sum, unsigned char maxval, int newcolors ) +{ + acolorhist_vector acolormap; + box_vector bv; + register int bi, i; + int boxes; + + bv = (box_vector) malloc( sizeof(struct box) * newcolors ); + acolormap = + (acolorhist_vector) malloc( sizeof(struct acolorhist_item) * newcolors); + if ( bv == (box_vector) 0 || acolormap == (acolorhist_vector) 0 ) { + fprintf( stderr, " out of memory allocating box vector\n" ); + fflush(stderr); + exit(6); + } + for ( i = 0; i < newcolors; ++i ) + PAM_ASSIGN( acolormap[i].acolor, 0, 0, 0, 0 ); + + /* + ** Set up the initial box. + */ + bv[0].ind = 0; + bv[0].colors = colors; + bv[0].sum = sum; + boxes = 1; + + /* + ** Main loop: split boxes until we have enough. + */ + while ( boxes < newcolors ) { + register int indx, clrs; + int sm; + register int minr, maxr, ming, mina, maxg, minb, maxb, maxa, v; + int halfsum, lowersum; + + /* + ** Find the first splittable box. + */ + for ( bi = 0; bi < boxes; ++bi ) + if ( bv[bi].colors >= 2 ) + break; + if ( bi == boxes ) + break; /* ran out of colors! */ + indx = bv[bi].ind; + clrs = bv[bi].colors; + sm = bv[bi].sum; + + /* + ** Go through the box finding the minimum and maximum of each + ** component - the boundaries of the box. + */ + minr = maxr = PAM_GETR( achv[indx].acolor ); + ming = maxg = PAM_GETG( achv[indx].acolor ); + minb = maxb = PAM_GETB( achv[indx].acolor ); + mina = maxa = PAM_GETA( achv[indx].acolor ); + for ( i = 1; i < clrs; ++i ) { + v = PAM_GETR( achv[indx + i].acolor ); + if ( v < minr ) minr = v; + if ( v > maxr ) maxr = v; + v = PAM_GETG( achv[indx + i].acolor ); + if ( v < ming ) ming = v; + if ( v > maxg ) maxg = v; + v = PAM_GETB( achv[indx + i].acolor ); + if ( v < minb ) minb = v; + if ( v > maxb ) maxb = v; + v = PAM_GETA( achv[indx + i].acolor ); + if ( v < mina ) mina = v; + if ( v > maxa ) maxa = v; + } + + /* + ** Find the largest dimension, and sort by that component. I have + ** included two methods for determining the "largest" dimension; + ** first by simply comparing the range in RGB space, and second + ** by transforming into luminosities before the comparison. You + ** can switch which method is used by switching the commenting on + ** the LARGE_ defines at the beginning of this source file. + */ +#ifdef LARGE_NORM + if ( maxa - mina >= maxr - minr && maxa - mina >= maxg - ming && maxa - mina >= maxb - minb ) + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + alphacompare ); + else if ( maxr - minr >= maxg - ming && maxr - minr >= maxb - minb ) + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + redcompare ); + else if ( maxg - ming >= maxb - minb ) + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + greencompare ); + else + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + bluecompare ); +#endif /*LARGE_NORM*/ +#ifdef LARGE_LUM + { + apixel p; + float rl, gl, bl, al; + + PAM_ASSIGN(p, maxr - minr, 0, 0, 0); + rl = PPM_LUMIN(p); + PAM_ASSIGN(p, 0, maxg - ming, 0, 0); + gl = PPM_LUMIN(p); + PAM_ASSIGN(p, 0, 0, maxb - minb, 0); + bl = PPM_LUMIN(p); + + /* + GRR: treat alpha as grayscale and assign (maxa - mina) to each of R, G, B? + assign (maxa - mina)/3 to each? + use alpha-fractional luminosity? (normalized_alpha * lum(r,g,b)) + al = dunno ... + [probably should read Heckbert's paper to decide] + */ + + if ( al >= rl && al >= gl && al >= bl ) + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + alphacompare ); + else if ( rl >= gl && rl >= bl ) + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + redcompare ); + else if ( gl >= bl ) + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + greencompare ); + else + qsort( + (char*) &(achv[indx]), clrs, sizeof(struct acolorhist_item), + bluecompare ); + } +#endif /*LARGE_LUM*/ + + /* + ** Now find the median based on the counts, so that about half the + ** pixels (not colors, pixels) are in each subdivision. + */ + lowersum = achv[indx].value; + halfsum = sm / 2; + for ( i = 1; i < clrs - 1; ++i ) { + if ( lowersum >= halfsum ) + break; + lowersum += achv[indx + i].value; + } + + /* + ** Split the box, and sort to bring the biggest boxes to the top. + */ + bv[bi].colors = i; + bv[bi].sum = lowersum; + bv[boxes].ind = indx + i; + bv[boxes].colors = clrs - i; + bv[boxes].sum = sm - lowersum; + ++boxes; + qsort( (char*) bv, boxes, sizeof(struct box), sumcompare ); + } + + /* + ** Ok, we've got enough boxes. Now choose a representative color for + ** each box. There are a number of possible ways to make this choice. + ** One would be to choose the center of the box; this ignores any structure + ** within the boxes. Another method would be to average all the colors in + ** the box - this is the method specified in Heckbert's paper. A third + ** method is to average all the pixels in the box. You can switch which + ** method is used by switching the commenting on the REP_ defines at + ** the beginning of this source file. + */ + for ( bi = 0; bi < boxes; ++bi ) { +#ifdef REP_CENTER_BOX + register int indx = bv[bi].ind; + register int clrs = bv[bi].colors; + register int minr, maxr, ming, maxg, minb, maxb, mina, maxa, v; + + minr = maxr = PAM_GETR( achv[indx].acolor ); + ming = maxg = PAM_GETG( achv[indx].acolor ); + minb = maxb = PAM_GETB( achv[indx].acolor ); + mina = maxa = PAM_GETA( achv[indx].acolor ); + for ( i = 1; i < clrs; ++i ) { + v = PAM_GETR( achv[indx + i].acolor ); + minr = min( minr, v ); + maxr = max( maxr, v ); + v = PAM_GETG( achv[indx + i].acolor ); + ming = min( ming, v ); + maxg = max( maxg, v ); + v = PAM_GETB( achv[indx + i].acolor ); + minb = min( minb, v ); + maxb = max( maxb, v ); + v = PAM_GETA( achv[indx + i].acolor ); + mina = min( mina, v ); + maxa = max( maxa, v ); + } + PAM_ASSIGN( + acolormap[bi].acolor, ( minr + maxr ) / 2, ( ming + maxg ) / 2, + ( minb + maxb ) / 2, ( mina + maxa ) / 2 ); +#endif /*REP_CENTER_BOX*/ +#ifdef REP_AVERAGE_COLORS + register int indx = bv[bi].ind; + register int clrs = bv[bi].colors; + register long r = 0, g = 0, b = 0, a = 0; + + for ( i = 0; i < clrs; ++i ) { + r += PAM_GETR( achv[indx + i].acolor ); + g += PAM_GETG( achv[indx + i].acolor ); + b += PAM_GETB( achv[indx + i].acolor ); + a += PAM_GETA( achv[indx + i].acolor ); + } + r = r / clrs; + g = g / clrs; + b = b / clrs; + a = a / clrs; + PAM_ASSIGN( acolormap[bi].acolor, r, g, b, a ); +#endif /*REP_AVERAGE_COLORS*/ +#ifdef REP_AVERAGE_PIXELS + register int indx = bv[bi].ind; + register int clrs = bv[bi].colors; + register long r = 0, g = 0, b = 0, a = 0, sum = 0; + + for ( i = 0; i < clrs; ++i ) { + r += PAM_GETR( achv[indx + i].acolor ) * achv[indx + i].value; + g += PAM_GETG( achv[indx + i].acolor ) * achv[indx + i].value; + b += PAM_GETB( achv[indx + i].acolor ) * achv[indx + i].value; + a += PAM_GETA( achv[indx + i].acolor ) * achv[indx + i].value; + sum += achv[indx + i].value; + } + r = r / sum; + if ( r > maxval ) r = maxval; /* avoid math errors */ + g = g / sum; + if ( g > maxval ) g = maxval; + b = b / sum; + if ( b > maxval ) b = maxval; + a = a / sum; + if ( a > maxval ) a = maxval; + /* GRR 20001228: added casts to quiet warnings; 255 DEPENDENCY */ + PAM_ASSIGN( acolormap[bi].acolor, (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a ); +#endif /*REP_AVERAGE_PIXELS*/ + } + + /* + ** All done. + */ + free(bv); + return acolormap; +} + +int +redcompare( const void *ch1, const void *ch2 ) +{ + return (int) PAM_GETR( ((acolorhist_vector)ch1)->acolor ) - + (int) PAM_GETR( ((acolorhist_vector)ch2)->acolor ); +} + +int +greencompare( const void *ch1, const void *ch2 ) +{ + return (int) PAM_GETG( ((acolorhist_vector)ch1)->acolor ) - + (int) PAM_GETG( ((acolorhist_vector)ch2)->acolor ); +} + +int +bluecompare( const void *ch1, const void *ch2 ) +{ + return (int) PAM_GETB( ((acolorhist_vector)ch1)->acolor ) - + (int) PAM_GETB( ((acolorhist_vector)ch2)->acolor ); +} + +int +alphacompare( const void *ch1, const void *ch2 ) +{ + return (int) PAM_GETA( ((acolorhist_vector)ch1)->acolor ) - + (int) PAM_GETA( ((acolorhist_vector)ch2)->acolor ); +} + +int +sumcompare( const void *b1, const void *b2 ) +{ + return ((box_vector)b2)->sum - + ((box_vector)b1)->sum; +} + +/*===========================================================================*/ + + +/* libpam3.c - pam (portable alpha map) utility library part 3 + ** + ** Colormap routines. + ** + ** Copyright (C) 1989, 1991 by Jef Poskanzer. + ** Copyright (C) 1997 by Greg Roelofs. + ** + ** Permission to use, copy, modify, and distribute this software and its + ** documentation for any purpose and without fee is hereby granted, provided + ** that the above copyright notice appear in all copies and that both that + ** copyright notice and this permission notice appear in supporting + ** documentation. This software is provided "as is" without express or + ** implied warranty. + */ + +acolorhist_vector +pam_computeacolorhist( apixels, cols, rows, maxacolors, acolorsP ) +rgbaPixel** apixels; +int cols, rows, maxacolors; +int* acolorsP; +{ + acolorhash_table acht; + acolorhist_vector achv; + + acht = pam_computeacolorhash( apixels, cols, rows, maxacolors, acolorsP ); + if ( acht == (acolorhash_table) 0 ) + return (acolorhist_vector) 0; + achv = pam_acolorhashtoacolorhist( acht, maxacolors ); + pam_freeacolorhash( acht ); + return achv; +} + + + +acolorhash_table +pam_computeacolorhash( apixels, cols, rows, maxacolors, acolorsP ) +rgbaPixel** apixels; +int cols, rows, maxacolors; +int* acolorsP; +{ + acolorhash_table acht; + register rgbaPixel* pP; + acolorhist_list achl; + int col, row, hash; + + acht = pam_allocacolorhash( ); + *acolorsP = 0; + + /* Go through the entire image, building a hash table of colors. */ + for ( row = 0; row < rows; ++row ) + for ( col = 0, pP = apixels[row]; col < cols; ++col, ++pP ) { + hash = pam_hashapixel( *pP ); + for ( achl = acht[hash]; achl != (acolorhist_list) 0; achl = achl->next ) + if ( PAM_EQUAL( achl->ch.acolor, *pP ) ) + break; + if ( achl != (acolorhist_list) 0 ) + ++(achl->ch.value); + else { + if ( ++(*acolorsP) > maxacolors ) { + pam_freeacolorhash( acht ); + return (acolorhash_table) 0; + } + achl = (acolorhist_list) malloc( sizeof(struct acolorhist_list_item) ); + if ( achl == 0 ) { + fprintf( stderr, " out of memory computing hash table\n" ); + exit(7); + } + achl->ch.acolor = *pP; + achl->ch.value = 1; + achl->next = acht[hash]; + acht[hash] = achl; + } + } + + return acht; +} + + + +acolorhash_table +pam_allocacolorhash( ) +{ + acolorhash_table acht; + int i; + + acht = (acolorhash_table) malloc( HASH_SIZE * sizeof(acolorhist_list) ); + if ( acht == 0 ) { + fprintf( stderr, " out of memory allocating hash table\n" ); + exit(8); + } + + for ( i = 0; i < HASH_SIZE; ++i ) + acht[i] = (acolorhist_list) 0; + + return acht; +} + + + +int +pam_addtoacolorhash( acht, acolorP, value ) +acolorhash_table acht; +rgbaPixel* acolorP; +int value; +{ + register int hash; + register acolorhist_list achl; + + achl = (acolorhist_list) malloc( sizeof(struct acolorhist_list_item) ); + if ( achl == 0 ) + return -1; + hash = pam_hashapixel( *acolorP ); + achl->ch.acolor = *acolorP; + achl->ch.value = value; + achl->next = acht[hash]; + acht[hash] = achl; + return 0; +} + + + +acolorhist_vector +pam_acolorhashtoacolorhist( acht, maxacolors ) +acolorhash_table acht; +int maxacolors; +{ + acolorhist_vector achv; + acolorhist_list achl; + int i, j; + + /* Now collate the hash table into a simple acolorhist array. */ + achv = (acolorhist_vector) malloc( maxacolors * sizeof(struct acolorhist_item) ); + /* (Leave room for expansion by caller.) */ + if ( achv == (acolorhist_vector) 0 ) { + fprintf( stderr, " out of memory generating histogram\n" ); + exit(9); + } + + /* Loop through the hash table. */ + j = 0; + for ( i = 0; i < HASH_SIZE; ++i ) + for ( achl = acht[i]; achl != (acolorhist_list) 0; achl = achl->next ) { + /* Add the new entry. */ + achv[j] = achl->ch; + ++j; + } + + /* All done. */ + return achv; +} + +int +pam_lookupacolor( acht, acolorP ) +acolorhash_table acht; +rgbaPixel* acolorP; +{ + int hash; + acolorhist_list achl; + + hash = pam_hashapixel( *acolorP ); + for ( achl = acht[hash]; achl != (acolorhist_list) 0; achl = achl->next ) + if ( PAM_EQUAL( achl->ch.acolor, *acolorP ) ) + return achl->ch.value; + + return -1; +} + +void +pam_freeacolorhist( achv ) +acolorhist_vector achv; +{ + free( (char*) achv ); +} + +void +pam_freeacolorhash( acht ) +acolorhash_table acht; +{ + int i; + acolorhist_list achl, achlnext; + + for ( i = 0; i < HASH_SIZE; ++i ) + for ( achl = acht[i]; achl != (acolorhist_list) 0; achl = achlnext ) { + achlnext = achl->next; + free( (char*) achl ); + } + free( (char*) acht ); +} + +/** \endcond DONOTDOCUMENT */ diff --git a/lib/service_wms.c b/lib/service_wms.c index cfabf186..1102e7bb 100644 --- a/lib/service_wms.c +++ b/lib/service_wms.c @@ -200,7 +200,7 @@ void _create_capabilities_wms(mapcache_context *ctx, mapcache_request_get_capabi layerxml = ezxml_add_child(toplayer,"Layer",0); ezxml_set_attr(layerxml, "cascaded", "1"); ezxml_set_attr(layerxml, "queryable", (tileset->source && tileset->source->info_formats)?"1":"0"); - + ezxml_set_txt(ezxml_add_child(layerxml,"Name",0),tileset->name); tsxml = ezxml_add_child(vendorxml, "TileSet",0); @@ -336,7 +336,6 @@ void _mapcache_service_wms_parse_request(mapcache_context *ctx, mapcache_service mapcache_service_wms *wms_service = (mapcache_service_wms*)this; *request = NULL; - str = apr_table_get(params,"SERVICE"); if(!str) { /* service is optional if we have a getmap */ @@ -713,7 +712,6 @@ void _mapcache_service_wms_parse_request(mapcache_context *ctx, mapcache_service apr_table_set(map_req->maps[map_req->nmaps-1]->dimensions,tileset->timedimension->key, APR_ARRAY_IDX(timedim_selected,i,char*)); } - } } }