/* ** © 2009-2015 by Kornel Lesiński. ** © 1989, 1991 by Jef Poskanzer. ** © 1997, 2000, 2002 by Greg Roelofs; based on an idea by Stefan Schneider. ** ** See COPYRIGHT file for license. */ #include "libimagequant.h" #include "pam.h" #include "nearest.h" #include "mempool.h" #include typedef struct vp_sort_tmp { float distance_squared; unsigned int idx; } vp_sort_tmp; typedef struct vp_search_tmp { float distance; unsigned int idx; int exclude; } vp_search_tmp; typedef struct vp_node { struct vp_node *near, *far; f_pixel vantage_point; float radius; unsigned int idx; } vp_node; struct nearest_map { vp_node *root; const colormap_item *palette; float nearest_other_color_dist[256]; mempoolptr mempool; }; static void vp_search_node(const vp_node *node, const f_pixel *const needle, vp_search_tmp *const best_candidate); static int vp_compare_distance(const void *ap, const void *bp) { float a = ((const vp_sort_tmp*)ap)->distance_squared; float b = ((const vp_sort_tmp*)bp)->distance_squared; return a > b ? 1 : -1; } static void vp_sort_indexes_by_distance(const f_pixel vantage_point, vp_sort_tmp indexes[], int num_indexes, const colormap_item items[]) { for(int i=0; i < num_indexes; i++) { indexes[i].distance_squared = colordifference(vantage_point, items[indexes[i].idx].acolor); } qsort(indexes, num_indexes, sizeof(indexes[0]), vp_compare_distance); } /* * Usually it should pick farthest point, but picking most popular point seems to make search quicker anyway */ static int vp_find_best_vantage_point_index(vp_sort_tmp indexes[], int num_indexes, const colormap_item items[]) { int best = 0; float best_popularity = items[indexes[0].idx].popularity; for(int i = 1; i < num_indexes; i++) { if (items[indexes[i].idx].popularity > best_popularity) { best_popularity = items[indexes[i].idx].popularity; best = i; } } return best; } static vp_node *vp_create_node(mempoolptr *m, vp_sort_tmp indexes[], int num_indexes, const colormap_item items[]) { if (num_indexes <= 0) { return NULL; } vp_node *node = mempool_alloc(m, sizeof(node[0]), 0); if (num_indexes == 1) { *node = (vp_node){ .vantage_point = items[indexes[0].idx].acolor, .idx = indexes[0].idx, .radius = MAX_DIFF, }; return node; } const int ref = vp_find_best_vantage_point_index(indexes, num_indexes, items); const int ref_idx = indexes[ref].idx; // Removes the `ref_idx` item from remaining items, because it's included in the current node num_indexes -= 1; indexes[ref] = indexes[num_indexes]; vp_sort_indexes_by_distance(items[ref_idx].acolor, indexes, num_indexes, items); // Remaining items are split by the median distance const int half_idx = num_indexes/2; *node = (vp_node){ .vantage_point = items[ref_idx].acolor, .idx = ref_idx, .radius = sqrtf(indexes[half_idx].distance_squared), }; node->near = vp_create_node(m, indexes, half_idx, items); node->far = vp_create_node(m, &indexes[half_idx], num_indexes - half_idx, items); return node; } LIQ_PRIVATE struct nearest_map *nearest_init(const colormap *map) { mempoolptr m = NULL; struct nearest_map *handle = mempool_create(&m, sizeof(handle[0]), sizeof(handle[0]) + sizeof(vp_node)*map->colors+16, map->malloc, map->free); LIQ_ARRAY(vp_sort_tmp, indexes, map->colors); for(unsigned int i=0; i < map->colors; i++) { indexes[i].idx = i; } vp_node *root = vp_create_node(&m, indexes, map->colors, map->palette); *handle = (struct nearest_map){ .root = root, .palette = map->palette, .mempool = m, }; for(unsigned int i=0; i < map->colors; i++) { vp_search_tmp best = { .distance = MAX_DIFF, .exclude = i, }; vp_search_node(root, &map->palette[i].acolor, &best); handle->nearest_other_color_dist[i] = best.distance * best.distance / 4.0; // half of squared distance } return handle; } static void vp_search_node(const vp_node *node, const f_pixel *const needle, vp_search_tmp *const best_candidate) { do { const float distance = sqrtf(colordifference(node->vantage_point, *needle)); if (distance < best_candidate->distance && best_candidate->exclude != node->idx) { best_candidate->distance = distance; best_candidate->idx = node->idx; } // Recurse towards most likely candidate first to narrow best candidate's distance as soon as possible if (distance < node->radius) { if (node->near) { vp_search_node(node->near, needle, best_candidate); } // The best node (final answer) may be just ouside the radius, but not farther than // the best distance we know so far. The vp_search_node above should have narrowed // best_candidate->distance, so this path is rarely taken. if (node->far && distance >= node->radius - best_candidate->distance) { node = node->far; // Fast tail recursion } else { break; } } else { if (node->far) { vp_search_node(node->far, needle, best_candidate); } if (node->near && distance <= node->radius + best_candidate->distance) { node = node->near; // Fast tail recursion } else { break; } } } while(true); } LIQ_PRIVATE unsigned int nearest_search(const struct nearest_map *handle, const f_pixel *px, const int likely_colormap_index, float *diff) { const float guess_diff = colordifference(handle->palette[likely_colormap_index].acolor, *px); if (guess_diff < handle->nearest_other_color_dist[likely_colormap_index]) { if (diff) *diff = guess_diff; return likely_colormap_index; } vp_search_tmp best_candidate = { .distance = sqrtf(guess_diff), .idx = likely_colormap_index, .exclude = -1, }; vp_search_node(handle->root, px, &best_candidate); if (diff) { *diff = best_candidate.distance * best_candidate.distance; } return best_candidate.idx; } LIQ_PRIVATE void nearest_free(struct nearest_map *centroids) { mempool_destroy(centroids->mempool); }