2014-02-10 02:10:30 +01:00
|
|
|
/*************************************************************************/
|
|
|
|
/* image_quantize.cpp */
|
|
|
|
/*************************************************************************/
|
|
|
|
/* This file is part of: */
|
|
|
|
/* GODOT ENGINE */
|
|
|
|
/* http://www.godotengine.org */
|
|
|
|
/*************************************************************************/
|
2017-01-01 22:01:57 +01:00
|
|
|
/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */
|
2014-02-10 02:10:30 +01:00
|
|
|
/* */
|
|
|
|
/* 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 or substantial portions of the 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 "image.h"
|
|
|
|
#include "print_string.h"
|
2017-03-19 00:36:26 +01:00
|
|
|
#include <stdio.h>
|
2014-02-10 02:10:30 +01:00
|
|
|
#ifdef TOOLS_ENABLED
|
2017-03-19 00:36:26 +01:00
|
|
|
#include "os/os.h"
|
2014-02-10 02:10:30 +01:00
|
|
|
#include "set.h"
|
|
|
|
#include "sort.h"
|
|
|
|
|
|
|
|
//#define QUANTIZE_SPEED_OVER_QUALITY
|
|
|
|
|
|
|
|
Image::MCBlock::MCBlock() {
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
Image::MCBlock::MCBlock(BColorPos *p_colors, int p_color_count) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
colors = p_colors;
|
|
|
|
color_count = p_color_count;
|
|
|
|
min_color.color = BColor(255, 255, 255, 255);
|
|
|
|
max_color.color = BColor(0, 0, 0, 0);
|
2014-02-10 02:10:30 +01:00
|
|
|
shrink();
|
|
|
|
}
|
|
|
|
|
|
|
|
int Image::MCBlock::get_longest_axis_index() const {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int max_dist = -1;
|
|
|
|
int max_index = 0;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < 4; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int d = max_color.color.col[i] - min_color.color.col[i];
|
|
|
|
if (d > max_dist) {
|
|
|
|
max_index = i;
|
|
|
|
max_dist = d;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return max_index;
|
|
|
|
}
|
|
|
|
int Image::MCBlock::get_longest_axis_length() const {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int max_dist = -1;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < 4; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int d = max_color.color.col[i] - min_color.color.col[i];
|
|
|
|
if (d > max_dist) {
|
|
|
|
max_dist = d;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return max_dist;
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
bool Image::MCBlock::operator<(const MCBlock &p_block) const {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
int alen = get_longest_axis_length();
|
|
|
|
int blen = p_block.get_longest_axis_length();
|
2017-03-19 00:36:26 +01:00
|
|
|
if (alen == blen) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
return colors < p_block.colors;
|
|
|
|
} else
|
|
|
|
return alen < blen;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Image::MCBlock::shrink() {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
min_color = colors[0];
|
|
|
|
max_color = colors[0];
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 1; i < color_count; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int j = 0; j < 4; j++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
min_color.color.col[j] = MIN(min_color.color.col[j], colors[i].color.col[j]);
|
|
|
|
max_color.color.col[j] = MAX(max_color.color.col[j], colors[i].color.col[j]);
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Image::quantize() {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
bool has_alpha = detect_alpha() != ALPHA_NONE;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
bool quantize_fast = OS::get_singleton()->has_environment("QUANTIZE_FAST");
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
convert(FORMAT_RGBA);
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
ERR_FAIL_COND(format != FORMAT_RGBA);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
DVector<uint8_t> indexed_data;
|
|
|
|
|
|
|
|
{
|
2017-03-19 00:36:26 +01:00
|
|
|
int color_count = data.size() / 4;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
ERR_FAIL_COND(color_count == 0);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
Set<MCBlock> block_queue;
|
|
|
|
|
|
|
|
DVector<BColorPos> data_colors;
|
|
|
|
data_colors.resize(color_count);
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
DVector<BColorPos>::Write dcw = data_colors.write();
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
DVector<uint8_t>::Read dr = data.read();
|
2017-03-19 00:36:26 +01:00
|
|
|
const BColor *drptr = (const BColor *)&dr[0];
|
|
|
|
BColorPos *bcptr = &dcw[0];
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
{
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < color_count; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
//uint32_t data_ofs=i<<2;
|
2017-03-19 00:36:26 +01:00
|
|
|
bcptr[i].color = drptr[i]; //BColor(drptr[data_ofs+0],drptr[data_ofs+1],drptr[data_ofs+2],drptr[data_ofs+3]);
|
|
|
|
bcptr[i].index = i;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//printf("color count: %i\n",color_count);
|
|
|
|
/*
|
|
|
|
for(int i=0;i<color_count;i++) {
|
|
|
|
|
|
|
|
BColor bc = ((BColor*)&wb[0])[i];
|
|
|
|
printf("%i - %i,%i,%i,%i\n",i,bc.r,bc.g,bc.b,bc.a);
|
|
|
|
}*/
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
MCBlock initial_block((BColorPos *)&dcw[0], color_count);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
block_queue.insert(initial_block);
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
while (block_queue.size() < 256 && block_queue.back()->get().color_count > 1) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
MCBlock longest = block_queue.back()->get();
|
|
|
|
//printf("longest: %i (%i)\n",longest.get_longest_axis_index(),longest.get_longest_axis_length());
|
|
|
|
|
|
|
|
block_queue.erase(block_queue.back());
|
|
|
|
|
|
|
|
BColorPos *first = longest.colors;
|
2017-03-19 00:36:26 +01:00
|
|
|
BColorPos *median = longest.colors + (longest.color_count + 1) / 2;
|
2014-02-10 02:10:30 +01:00
|
|
|
BColorPos *end = longest.colors + longest.color_count;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
int lai =longest.get_longest_axis_index();
|
|
|
|
switch(lai) {
|
|
|
|
#if 0
|
|
|
|
case 0: { SortArray<BColorPos,BColorPos::SortR> sort; sort.sort(first,end-first); } break;
|
|
|
|
case 1: { SortArray<BColorPos,BColorPos::SortG> sort; sort.sort(first,end-first); } break;
|
|
|
|
case 2: { SortArray<BColorPos,BColorPos::SortB> sort; sort.sort(first,end-first); } break;
|
|
|
|
case 3: { SortArray<BColorPos,BColorPos::SortA> sort; sort.sort(first,end-first); } break;
|
|
|
|
#else
|
|
|
|
case 0: { SortArray<BColorPos,BColorPos::SortR> sort; sort.nth_element(0,end-first,median-first,first); } break;
|
|
|
|
case 1: { SortArray<BColorPos,BColorPos::SortG> sort; sort.nth_element(0,end-first,median-first,first); } break;
|
|
|
|
case 2: { SortArray<BColorPos,BColorPos::SortB> sort; sort.nth_element(0,end-first,median-first,first); } break;
|
|
|
|
case 3: { SortArray<BColorPos,BColorPos::SortA> sort; sort.nth_element(0,end-first,median-first,first); } break;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
//avoid same color from being split in 2
|
|
|
|
//search forward and flip
|
|
|
|
BColorPos *median_end=median;
|
|
|
|
BColorPos *p=median_end+1;
|
|
|
|
|
|
|
|
while(p!=end) {
|
|
|
|
if (median_end->color==p->color) {
|
|
|
|
SWAP(*(median_end+1),*p);
|
|
|
|
median_end++;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//search backward and flip
|
|
|
|
BColorPos *median_begin=median;
|
|
|
|
p=median_begin-1;
|
|
|
|
|
|
|
|
while(p!=(first-1)) {
|
|
|
|
if (median_begin->color==p->color) {
|
|
|
|
SWAP(*(median_begin-1),*p);
|
|
|
|
median_begin--;
|
|
|
|
}
|
|
|
|
p--;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (first < median_begin) {
|
|
|
|
median=median_begin;
|
|
|
|
} else if (median_end < end-1) {
|
|
|
|
median=median_end+1;
|
|
|
|
} else {
|
|
|
|
break; //shouldn't have arrived here, since it means all pixels are equal, but wathever
|
|
|
|
}
|
|
|
|
|
|
|
|
MCBlock left(first,median-first);
|
|
|
|
MCBlock right(median,end-median);
|
|
|
|
|
|
|
|
block_queue.insert(left);
|
|
|
|
block_queue.insert(right);
|
|
|
|
|
|
|
|
#else
|
2017-03-19 00:36:26 +01:00
|
|
|
switch (longest.get_longest_axis_index()) {
|
|
|
|
case 0: {
|
|
|
|
SortArray<BColorPos, BColorPos::SortR> sort;
|
|
|
|
sort.nth_element(0, end - first, median - first, first);
|
|
|
|
} break;
|
|
|
|
case 1: {
|
|
|
|
SortArray<BColorPos, BColorPos::SortG> sort;
|
|
|
|
sort.nth_element(0, end - first, median - first, first);
|
|
|
|
} break;
|
|
|
|
case 2: {
|
|
|
|
SortArray<BColorPos, BColorPos::SortB> sort;
|
|
|
|
sort.nth_element(0, end - first, median - first, first);
|
|
|
|
} break;
|
|
|
|
case 3: {
|
|
|
|
SortArray<BColorPos, BColorPos::SortA> sort;
|
|
|
|
sort.nth_element(0, end - first, median - first, first);
|
|
|
|
} break;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
MCBlock left(first, median - first);
|
|
|
|
MCBlock right(median, end - median);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
block_queue.insert(left);
|
|
|
|
block_queue.insert(right);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
while (block_queue.size() > 256) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
block_queue.erase(block_queue.front()); // erase least significant
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int res_colors = 0;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
int comp_size = (has_alpha ? 4 : 3);
|
|
|
|
indexed_data.resize(color_count + 256 * comp_size);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
DVector<uint8_t>::Write iw = indexed_data.write();
|
2017-03-19 00:36:26 +01:00
|
|
|
uint8_t *iwptr = &iw[0];
|
2014-02-10 02:10:30 +01:00
|
|
|
BColor pallete[256];
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
// print_line("applying quantization - res colors "+itos(block_queue.size()));
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
while (block_queue.size()) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
const MCBlock &b = block_queue.back()->get();
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
uint64_t sum[4] = { 0, 0, 0, 0 };
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < b.color_count; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
sum[0] += b.colors[i].color.col[0];
|
|
|
|
sum[1] += b.colors[i].color.col[1];
|
|
|
|
sum[2] += b.colors[i].color.col[2];
|
|
|
|
sum[3] += b.colors[i].color.col[3];
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
BColor c(sum[0] / b.color_count, sum[1] / b.color_count, sum[2] / b.color_count, sum[3] / b.color_count);
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
//printf(" %i: %i,%i,%i,%i out of %i\n",res_colors,c.r,c.g,c.b,c.a,b.color_count);
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < comp_size; i++) {
|
|
|
|
iwptr[color_count + res_colors * comp_size + i] = c.col[i];
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (quantize_fast) {
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < b.color_count; i++) {
|
|
|
|
iwptr[b.colors[i].index] = res_colors;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
pallete[res_colors] = c;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
res_colors++;
|
|
|
|
|
|
|
|
block_queue.erase(block_queue.back());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!quantize_fast) {
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int i = 0; i < color_count; i++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const BColor &c = drptr[i];
|
|
|
|
uint8_t best_dist_idx = 0;
|
|
|
|
uint32_t dist = 0xFFFFFFFF;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
for (int j = 0; j < res_colors; j++) {
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
const BColor &pc = pallete[j];
|
2014-02-10 02:10:30 +01:00
|
|
|
uint32_t d = 0;
|
2017-03-19 00:36:26 +01:00
|
|
|
{
|
|
|
|
int16_t v = (int16_t)c.r - (int16_t)pc.r;
|
|
|
|
d += v * v;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
int16_t v = (int16_t)c.g - (int16_t)pc.g;
|
|
|
|
d += v * v;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
int16_t v = (int16_t)c.b - (int16_t)pc.b;
|
|
|
|
d += v * v;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
int16_t v = (int16_t)c.a - (int16_t)pc.a;
|
|
|
|
d += v * v;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
if (d <= dist) {
|
|
|
|
best_dist_idx = j;
|
|
|
|
dist = d;
|
|
|
|
}
|
|
|
|
}
|
2014-02-10 02:10:30 +01:00
|
|
|
|
2017-03-19 00:36:26 +01:00
|
|
|
iwptr[i] = best_dist_idx;
|
2014-02-10 02:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//iw = DVector<uint8_t>::Write();
|
|
|
|
//dr = DVector<uint8_t>::Read();
|
|
|
|
//wb = DVector<uint8_t>::Write();
|
|
|
|
}
|
|
|
|
|
|
|
|
print_line(itos(indexed_data.size()));
|
2017-03-19 00:36:26 +01:00
|
|
|
data = indexed_data;
|
|
|
|
format = has_alpha ? FORMAT_INDEXED_ALPHA : FORMAT_INDEXED;
|
2014-02-10 02:10:30 +01:00
|
|
|
|
|
|
|
} //do none
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
void Image::quantize() {} //do none
|
|
|
|
|
|
|
|
#endif
|