Use left nearest block for endpoint index prediction

This change improves compression ratio.

Explanation:
In the original algorithm the relative position of the block, used for prediction of the endpoint index for the currently decoded block, depends on the chunk encoding type. It can be a horizontal neighbour, a vertical neighbour, a diagonal neighbour, or in some rare cases even a block at relative position (-2, 0) or (-3, 0). Using left nearest neighbour for endpoint index prediction for each block (except the blocks at the image borders) minimizes the average distance to the prediction block and therefore usually improves the endpoint index prediction.

Note:
This modification alters the output file format and makes it incompatible with the previous revisions.

Testing:
The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). All the decompressed test images are identical to the images being compressed and decompressed using original version of Crunch.

[Compressing Kodak set without mipmaps]
Original: 1582222 bytes / 28.838 sec
Modified: 1570534 bytes / 28.629 sec
Improvement: 0.74% (compression ratio) / 0.72% (compression time)

[Compressing Kodak set with mipmaps]
Original: 2065243 bytes / 36.977 sec
Modified: 2051509 bytes / 36.568 sec
Improvement: 0.67% (compression ratio) / 1.11% (compression time)
This commit is contained in:
Alexander Suvorov
2017-04-27 15:49:48 +02:00
parent 13b1faa48d
commit 8cc5f19ae5
4 changed files with 172 additions and 175 deletions
+68 -40
View File
@@ -727,8 +727,7 @@ bool crn_comp::pack_chunks(
}
}
uint prev_endpoint_index[cNumComps];
utils::zero_object(prev_endpoint_index);
uint endpoint_index[cNumComps][2][2] = {};
uint prev_selector_index[cNumComps];
utils::zero_object(prev_selector_index);
@@ -763,42 +762,52 @@ bool crn_comp::pack_chunks(
// endpoints
if (comp_index == cColor) {
if (pColor_endpoint_remap) {
for (uint i = 0; i < encoding.m_num_tiles; i++) {
uint cur_endpoint_index = (*pColor_endpoint_remap)[m_endpoint_indices[cColor][details.m_first_endpoint_index + i]];
int endpoint_delta = cur_endpoint_index - prev_endpoint_index[cColor];
for (uint y = 0; y < 2; y++) {
for (uint x = 0; x < 2; x++) {
uint8 endpoint_reference = details.m_endpoint_references[comp_index][y][x];
if (!endpoint_reference) {
endpoint_index[comp_index][y][x] = (*pColor_endpoint_remap)[details.m_endpoint_indices[comp_index][y][x]];
int endpoint_delta = endpoint_index[comp_index][y][x] - endpoint_index[comp_index][y][x ^ 1];
int sym = endpoint_delta;
if (sym < 0)
sym += pColor_endpoint_remap->size();
int sym = endpoint_delta;
if (sym < 0)
sym += pColor_endpoint_remap->size();
CRNLIB_ASSERT(sym >= 0 && sym < (int)pColor_endpoint_remap->size());
CRNLIB_ASSERT(sym >= 0 && sym < (int)pColor_endpoint_remap->size());
if (!pCodec)
m_endpoint_index_hist[cColor].inc_freq(sym);
else
pCodec->encode(sym, m_endpoint_index_dm[0]);
prev_endpoint_index[cColor] = cur_endpoint_index;
if (!pCodec)
m_endpoint_index_hist[0].inc_freq(sym);
else
pCodec->encode(sym, m_endpoint_index_dm[0]);
} else {
endpoint_index[comp_index][y][x] = endpoint_reference == 1 ? endpoint_index[comp_index][y][x ^ 1] : endpoint_index[comp_index][y ^ 1][x];
}
}
}
}
} else {
if (pAlpha_endpoint_remap) {
for (uint i = 0; i < encoding.m_num_tiles; i++) {
uint cur_endpoint_index = (*pAlpha_endpoint_remap)[m_endpoint_indices[comp_index][details.m_first_endpoint_index + i]];
int endpoint_delta = cur_endpoint_index - prev_endpoint_index[comp_index];
for (uint y = 0; y < 2; y++) {
for (uint x = 0; x < 2; x++) {
uint8 endpoint_reference = details.m_endpoint_references[comp_index][y][x];
if (!endpoint_reference) {
endpoint_index[comp_index][y][x] = (*pAlpha_endpoint_remap)[details.m_endpoint_indices[comp_index][y][x]];
int endpoint_delta = endpoint_index[comp_index][y][x] - endpoint_index[comp_index][y][x ^ 1];
int sym = endpoint_delta;
if (sym < 0)
sym += pAlpha_endpoint_remap->size();
int sym = endpoint_delta;
if (sym < 0)
sym += pAlpha_endpoint_remap->size();
CRNLIB_ASSERT(sym >= 0 && sym < (int)pAlpha_endpoint_remap->size());
CRNLIB_ASSERT(sym >= 0 && sym < (int)pAlpha_endpoint_remap->size());
if (!pCodec)
m_endpoint_index_hist[1].inc_freq(sym);
else
pCodec->encode(sym, m_endpoint_index_dm[1]);
prev_endpoint_index[comp_index] = cur_endpoint_index;
if (!pCodec)
m_endpoint_index_hist[1].inc_freq(sym);
else
pCodec->encode(sym, m_endpoint_index_dm[1]);
} else {
endpoint_index[comp_index][y][x] = endpoint_reference == 1 ? endpoint_index[comp_index][y][x ^ 1] : endpoint_index[comp_index][y ^ 1][x];
}
}
}
}
}
@@ -814,7 +823,7 @@ bool crn_comp::pack_chunks(
if (comp_index == cColor) {
if (pColor_selector_remap) {
uint cur_selector_index = (*pColor_selector_remap)[m_selector_indices[cColor][details.m_first_selector_index + x + y * 2]];
uint cur_selector_index = (*pColor_selector_remap)[details.m_selector_indices[cColor][y][x]];
int selector_delta = cur_selector_index - prev_selector_index[cColor];
int sym = selector_delta;
@@ -831,7 +840,7 @@ bool crn_comp::pack_chunks(
prev_selector_index[cColor] = cur_selector_index;
}
} else if (pAlpha_selector_remap) {
uint cur_selector_index = (*pAlpha_selector_remap)[m_selector_indices[comp_index][details.m_first_selector_index + x + y * 2]];
uint cur_selector_index = (*pAlpha_selector_remap)[details.m_selector_indices[comp_index][y][x]];
int selector_delta = cur_selector_index - prev_selector_index[comp_index];
int sym = selector_delta;
@@ -1251,6 +1260,28 @@ bool crn_comp::quantize_chunks() {
}
void crn_comp::create_chunk_indices() {
uint8 endpoint_index_map[8][4] = {
{ 0, 0, 0, 0 },
{ 0, 0, 1, 1 },
{ 0, 1, 0, 1 },
{ 0, 0, 1, 2 },
{ 1, 2, 0, 0 },
{ 0, 1, 0, 2 },
{ 1, 0, 2, 0 },
{ 0, 1, 2, 3 },
};
uint8 endpoint_references[8][4] = {
{ 0, 1, 2, 1 },
{ 0, 1, 0, 1 },
{ 0, 0, 2, 2 },
{ 0, 1, 0, 0 },
{ 0, 0, 0, 1 },
{ 0, 0, 2, 0 },
{ 0, 0, 0, 2 },
{ 0, 0, 0, 0 },
};
m_chunk_details.resize(m_total_chunks);
for (uint i = 0; i < cNumComps; i++) {
@@ -1261,14 +1292,6 @@ void crn_comp::create_chunk_indices() {
for (uint chunk_index = 0; chunk_index < m_total_chunks; chunk_index++) {
const dxt_hc::chunk_encoding& chunk_encoding = m_hvq.get_chunk_encoding(chunk_index);
for (uint i = 0; i < cNumComps; i++) {
if (m_has_comp[i]) {
m_chunk_details[chunk_index].m_first_endpoint_index = m_endpoint_indices[i].size();
m_chunk_details[chunk_index].m_first_selector_index = m_selector_indices[i].size();
break;
}
}
for (uint i = 0; i < cNumComps; i++) {
if (!m_has_comp[i])
continue;
@@ -1276,9 +1299,14 @@ void crn_comp::create_chunk_indices() {
for (uint tile_index = 0; tile_index < chunk_encoding.m_num_tiles; tile_index++)
m_endpoint_indices[i].push_back(chunk_encoding.m_endpoint_indices[i][tile_index]);
for (uint y = 0; y < cChunkBlockHeight; y++)
for (uint x = 0; x < cChunkBlockWidth; x++)
for (uint t = 0, y = 0; y < cChunkBlockHeight; y++) {
for (uint x = 0; x < cChunkBlockWidth; x++, t++) {
m_selector_indices[i].push_back(chunk_encoding.m_selector_indices[i][y][x]);
m_chunk_details[chunk_index].m_selector_indices[i][y][x] = chunk_encoding.m_selector_indices[i][y][x];
m_chunk_details[chunk_index].m_endpoint_indices[i][y][x] = chunk_encoding.m_endpoint_indices[i][endpoint_index_map[chunk_encoding.m_encoding_index][t]];
m_chunk_details[chunk_index].m_endpoint_references[i][y][x] = endpoint_references[chunk_encoding.m_encoding_index][t];
}
}
}
}
}