Evas TGV: Fix distortion of TGV images (ETC1)

Due to some invalid geometry considerations, there was a
1 pixel distortion in images, varying with the lz4 compressed
macro-block size.

Anyhow, I couldn't wrap my head around Cedric's code. So I rewrote
the whole thing instead, fixed it and improved the block size
selection (based on the image size, to optimize lz4 compression).
This commit is contained in:
Jean-Philippe Andre 2014-04-23 18:08:36 +09:00
parent 709c1d86c0
commit 28ce791dfa
2 changed files with 232 additions and 160 deletions

View File

@ -37,6 +37,11 @@
#define OFFSET_HEIGHT 12
#define OFFSET_BLOCKS 16
#undef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#undef MAX
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
/*---*/
@ -993,160 +998,186 @@ eet_data_image_lossless_compressed_convert(int *size,
}
}
static int
_block_size_get(int size)
{
static const int MAX_BLOCK = 6; // 256 pixels
int k = 0;
while ((4 << k) < size) k++;
k = MAX(0, k - 1);
if ((size * 3 / 2) >= (4 << k)) return MAX(0, MIN(k - 1, MAX_BLOCK));
return MIN(k, MAX_BLOCK);
}
static void *
eet_data_image_etc1_compressed_convert(int *size,
const unsigned char *data8,
unsigned int w,
unsigned int h,
int quality,
int compression)
int compress)
{
Eina_Binbuf *r;
rg_etc1_pack_params param;
unsigned char header[8] = "TGV1";
unsigned int nw, nh;
unsigned int block, block_count;
unsigned int x, y;
unsigned int compress_length;
unsigned int real_x, real_y;
unsigned int *data = (unsigned int *) data8;
char *comp;
char *buffer;
uint32_t *data;
uint32_t width, height;
uint8_t header[8] = "TGV1";
int block_width, block_height, macro_block_width, macro_block_height;
int block_count, image_stride, image_height;
Eina_Binbuf *r;
void *result;
r = eina_binbuf_new();
if (!r) return NULL;
nw = htonl(w);
nh = htonl(h);
image_stride = w;
image_height = h;
data = (uint32_t *) data8;
width = htonl(image_stride);
height = htonl(image_height);
// Disable dithering, as it will deteriorate the quality of flat surfaces
param.m_dithering = 0;
if (quality > 95)
{
param.m_quality = rg_etc1_high_quality;
block = 7;
}
param.m_quality = rg_etc1_high_quality;
else if (quality > 30)
{
param.m_quality = rg_etc1_medium_quality;
block = 6;
}
param.m_quality = rg_etc1_medium_quality;
else
{
param.m_quality = rg_etc1_low_quality;
block = 5;
}
param.m_quality = rg_etc1_low_quality;
header[4] = (block << 4) | block;
// header[4]: 4 bit block width, 4 bit block height
block_width = _block_size_get(image_stride + 2);
block_height = _block_size_get(image_height + 2);
header[4] = (block_height << 4) | block_width;
// header[5]: 0 for ETC1
header[5] = 0;
header[6] = (!!compression & 0x1); // For now only LZ4 compression
// header[6]: 0 for raw, 1, for LZ4 compressed
header[6] = (!!compress & 0x1);
// header[7]: options (unused)
header[7] = 0;
// Write header
eina_binbuf_append_length(r, header, sizeof (header));
eina_binbuf_append_length(r, (unsigned char*) &nw, sizeof (nw));
eina_binbuf_append_length(r, (unsigned char*) &nh, sizeof (nh));
eina_binbuf_append_length(r, (unsigned char*) &width, sizeof (width));
eina_binbuf_append_length(r, (unsigned char*) &height, sizeof (height));
block = 4 << block;
block_count = (block * block) / (4 * 4);
// Real block size in pixels, obviously a multiple of 4
macro_block_width = 4 << block_width;
macro_block_height = 4 << block_height;
// Number of ETC1 blocks in a compressed block
block_count = (macro_block_width * macro_block_height) / (4 * 4);
buffer = alloca(block_count * 8);
if (compression)
if (compress)
{
compress_length = LZ4_compressBound(block_count * 8);
comp = alloca(compress_length);
comp = alloca(LZ4_compressBound(block_count * 8));
}
else
{
comp = NULL;
}
// Write block
for (y = 0; y < h + 2; y += block)
// Write macro block
for (int y = 0; y < image_height + 2; y += macro_block_height)
{
real_y = y > 0 ? y - 1 : 0;
uint32_t *input, *last_col, *last_row, *last_pix;
int real_y;
int wlen;
for (x = 0; x < w + 2; x += block)
if (y == 0) real_y = 0;
else if (y < image_height + 1) real_y = y - 1;
else real_y = image_height - 1;
for (int x = 0; x < image_stride + 2; x += macro_block_width)
{
unsigned int i, j;
unsigned char duplicate_w[2], duplicate_h[2];
int wlen;
char *offset = buffer;
int real_x = x;
real_x = x > 0 ? x - 1 : 0;
if (x == 0) real_x = 0;
else if (x < image_stride + 1) real_x = x - 1;
else real_x = image_stride - 1;
for (i = 0; i < block; i += 4)
input = data + real_y * image_stride + real_x;
last_row = data + image_stride * (image_height - 1) + real_x;
last_col = data + (real_y + 1) * image_stride - 1;
last_pix = data + image_height * image_stride - 1;
for (int by = 0; by < macro_block_height; by += 4)
{
unsigned char block_h;
int kmax;
int dup_top = ((y + by) == 0) ? 1 : 0;
int max_row = MAX(0, MIN(4, image_height - real_y - by));
int oy = (y == 0) ? 1 : 0;
duplicate_h[0] = !!((real_y + i) == 0);
duplicate_h[1] = !!((real_y + i + (4 - duplicate_h[0])) >= h);
block_h = 4 - duplicate_h[0] - duplicate_h[1];
kmax = real_y + i + block_h < h ?
block_h : h - real_y - i - 1;
for (j = 0; j < block; j += 4)
for (int bx = 0; bx < macro_block_width; bx += 4)
{
unsigned char todo[64] = { 0 };
unsigned char block_w;
int block_length;
int k, lmax;
int dup_left = ((x + bx) == 0) ? 1 : 0;
int max_col = MAX(0, MIN(4, image_stride - real_x - bx));
uint32_t todo[16] = { 0 };
int row, col;
int ox = (x == 0) ? 1 : 0;
duplicate_w[0] = !!((real_x + j) == 0);
duplicate_w[1] = !!(((real_x + j + (4 - duplicate_w[0]))) >= w);
block_w = 4 - duplicate_w[0] - duplicate_w[1];
lmax = real_x + j + block_w < w ?
block_w : w - real_x - j - 1;
block_length = real_x + j + 4 < w ?
4 : w - real_x - j - 1;
if (lmax > 0)
if (dup_left)
{
for (k = 0; k < kmax; k++)
memcpy(&todo[(k + duplicate_h[0]) * 16 + duplicate_w[0] * 4],
&data[(real_y + i + k) * w + real_x + j],
4 * lmax);
// Duplicate left column
for (row = 0; row < max_row; row++)
todo[row * 4] = input[row * image_stride];
for (row = max_row; row < 4; row++)
todo[row * 4] = last_row[0];
}
if (duplicate_h[0] && block_length > 0) // Duplicate first line
memcpy(&todo[0],
&data[(real_y + i) * w + real_x + j],
block_length * 4);
if (duplicate_h[1] && block_length > 0 && kmax >= 0) // Duplicate last line
memcpy(&todo[kmax * 16],
&data[(real_y + i + kmax) * w + real_x + j],
block_length * 4);
if (duplicate_w[0]) // Duplicate first row
if (dup_top)
{
for (k = 0; k < kmax; k++)
memcpy(&todo[(k + duplicate_h[0]) * 16],
&data[(real_y + i + k) * w + real_x + j],
4); // Copy a pixel at a time
// Duplicate top row
for (col = 0; col < max_col; col++)
todo[col] = input[MAX(col + bx - ox, 0)];
for (col = max_col; col < 4; col++)
todo[col] = last_col[0];
}
if (duplicate_w[1] && lmax >= 0) // Duplicate last row
for (row = dup_top; row < 4; row++)
{
for (k = 0; k < kmax; k++)
memcpy(&todo[(k + duplicate_h[0]) * 16 + (duplicate_w[0] + lmax) * 4],
&data[(real_y + i + k) * w + real_x + j + lmax],
4); // Copy a pixel at a time
for (col = dup_left; col < max_col; col++)
{
if (row < max_row)
{
// Normal copy
todo[row * 4 + col] = input[(row + by - oy) * image_stride + bx + col - ox];
}
else
{
// Copy last line
todo[row * 4 + col] = last_row[col + bx - ox];
}
}
for (col = max_col; col < 4; col++)
{
// Right edge
if (row < max_row)
{
// Duplicate last column
todo[row * 4 + col] = last_col[MAX(row + by - oy, 0) * image_stride];
}
else
{
// Duplicate very last pixel again and again
todo[row * 4 + col] = *last_pix;
}
}
}
rg_etc1_pack_block(offset, (unsigned int*) todo, &param);
offset += 8;
}
}
if (compression)
if (compress)
{
wlen = LZ4_compressHC(buffer, comp, block_count * 8);
}

View File

@ -13,6 +13,18 @@
#include "lz4hc.h"
#include "rg_etc1.h"
static int
_block_size_get(int size)
{
static const int MAX_BLOCK = 6; // 256 pixels
int k = 0;
while ((4 << k) < size) k++;
k = MAX(0, k - 1);
if ((size * 3 / 2) >= (4 << k)) return MAX(0, MIN(k - 1, MAX_BLOCK));
return MIN(k, MAX_BLOCK);
}
static int
evas_image_save_file_tgv(RGBA_Image *im,
const char *file, const char *key EINA_UNUSED,
@ -25,11 +37,8 @@ evas_image_save_file_tgv(RGBA_Image *im,
uint32_t *data;
uint32_t width, height;
uint8_t header[8] = "TGV1";
unsigned int block;
unsigned int x, y;
unsigned int compress_length;
unsigned int block_count;
unsigned int real_x, real_y;
int block_width, block_height, macro_block_width, macro_block_height;
int block_count, image_stride, image_height;
if (!im || !im->image.data || !file)
return 0;
@ -38,17 +47,19 @@ evas_image_save_file_tgv(RGBA_Image *im,
if (im->cache_entry.flags.alpha)
return 0;
// Only RGBA encoding for now. TODO: Direct copy of ETC1/2 blocks.
if (im->cache_entry.space != EVAS_COLORSPACE_ARGB8888)
return 0;
image_stride = im->cache_entry.w;
image_height = im->cache_entry.h;
data = im->image.data;
width = htonl(im->cache_entry.w);
height = htonl(im->cache_entry.h);
width = htonl(image_stride);
height = htonl(image_height);
// Disable dithering, as it will deteriorate the quality of flat surfaces
param.m_dithering = 0;
// FIXME: Depending on the block size, we have some distortion of the image
// Usually, one or two pixels on the top & left borders are removed
block = 6;
if (quality > 95)
param.m_quality = rg_etc1_high_quality;
else if (quality > 30)
@ -56,9 +67,18 @@ evas_image_save_file_tgv(RGBA_Image *im,
else
param.m_quality = rg_etc1_low_quality;
header[4] = (block << 4) | block;
// header[4]: 4 bit block width, 4 bit block height
block_width = _block_size_get(image_stride + 2);
block_height = _block_size_get(image_height + 2);
header[4] = (block_height << 4) | block_width;
// header[5]: 0 for ETC1
header[5] = 0;
// header[6]: 0 for raw, 1, for LZ4 compressed
header[6] = (!!compress & 0x1);
// header[7]: options (unused)
header[7] = 0;
f = fopen(file, "w");
@ -69,88 +89,109 @@ evas_image_save_file_tgv(RGBA_Image *im,
if (fwrite(&width, sizeof (uint32_t), 1, f) != 1) goto on_error;
if (fwrite(&height, sizeof (uint32_t), 1, f) != 1) goto on_error;
block = 4 << block;
// Real block size in pixels, obviously a multiple of 4
macro_block_width = 4 << block_width;
macro_block_height = 4 << block_height;
block_count = (block * block) / (4 * 4);
// Number of ETC1 blocks in a compressed block
block_count = (macro_block_width * macro_block_height) / (4 * 4);
buffer = alloca(block_count * 8);
if (compress)
{
compress_length = LZ4_compressBound(block_count * 8);
comp = alloca(compress_length);
comp = alloca(LZ4_compressBound(block_count * 8));
}
else
{
comp = NULL;
}
// Write block
for (y = 0; y < im->cache_entry.h + 2; y += block)
// Write macro block
for (int y = 0; y < image_height + 2; y += macro_block_height)
{
real_y = y > 0 ? y - 1 : 0;
uint32_t *input, *last_col, *last_row, *last_pix;
int real_y;
int wlen;
for (x = 0; x < im->cache_entry.w + 2; x += block)
if (y == 0) real_y = 0;
else if (y < image_height + 1) real_y = y - 1;
else real_y = image_height - 1;
for (int x = 0; x < image_stride + 2; x += macro_block_width)
{
unsigned int i, j;
unsigned char duplicate_w[2], duplicate_h[2];
int wlen;
char *offset = buffer;
int real_x = x;
real_x = x > 0 ? x - 1 : 0;
if (x == 0) real_x = 0;
else if (x < image_stride + 1) real_x = x - 1;
else real_x = image_stride - 1;
for (i = 0; i < block; i += 4)
input = data + real_y * image_stride + real_x;
last_row = data + image_stride * (image_height - 1) + real_x;
last_col = data + (real_y + 1) * image_stride - 1;
last_pix = data + image_height * image_stride - 1;
for (int by = 0; by < macro_block_height; by += 4)
{
unsigned char block_h;
int kmax;
int dup_top = ((y + by) == 0) ? 1 : 0;
int max_row = MAX(0, MIN(4, image_height - real_y - by));
int oy = (y == 0) ? 1 : 0;
duplicate_h[0] = !!((real_y + i) == 0);
duplicate_h[1] = !!((real_y + i + (4 - duplicate_h[0])) >= im->cache_entry.h);
block_h = 4 - duplicate_h[0] - duplicate_h[1];
kmax = real_y + i + block_h < im->cache_entry.h ?
block_h : im->cache_entry.h - real_y - i - 1;
for (j = 0; j < block; j += 4)
for (int bx = 0; bx < macro_block_width; bx += 4)
{
unsigned char todo[64] = { 0 };
unsigned char block_w;
int block_length;
int k, lmax;
int dup_left = ((x + bx) == 0) ? 1 : 0;
int max_col = MAX(0, MIN(4, image_stride - real_x - bx));
uint32_t todo[16] = { 0 };
int row, col;
int ox = (x == 0) ? 1 : 0;
duplicate_w[0] = !!((real_x + j) == 0);
duplicate_w[1] = !!(((real_x + j + (4 - duplicate_w[0]))) >= im->cache_entry.w);
block_w = 4 - duplicate_w[0] - duplicate_w[1];
lmax = real_x + j + block_w < im->cache_entry.w ?
block_w : im->cache_entry.w - real_x - j - 1;
block_length = real_x + j + 4 < im->cache_entry.w ?
4 : im->cache_entry.w - real_x - j - 1;
if (lmax > 0)
if (dup_left)
{
for (k = 0; k < kmax; k++)
memcpy(&todo[(k + duplicate_h[0]) * 16 + duplicate_w[0] * 4],
&data[(real_y + i + k) * im->cache_entry.w + real_x + j],
4 * lmax);
// Duplicate left column
for (row = 0; row < max_row; row++)
todo[row * 4] = input[row * image_stride];
for (row = max_row; row < 4; row++)
todo[row * 4] = last_row[0];
}
if (duplicate_h[0] && block_length > 0) // Duplicate first line
memcpy(&todo[0], &data[(real_y + i) * im->cache_entry.w + real_x + j], block_length * 4);
if (duplicate_h[1] && block_length > 0 && kmax >= 0) // Duplicate last line
memcpy(&todo[kmax * 16], &data[(real_y + i + kmax) * im->cache_entry.w + real_x + j], block_length * 4);
if (duplicate_w[0]) // Duplicate first row
if (dup_top)
{
for (k = 0; k < kmax; k++)
memcpy(&todo[(k + duplicate_h[0]) * 16],
&data[(real_y + i + k) * im->cache_entry.w + real_x + j],
4); // Copy a pixel at a time
// Duplicate top row
for (col = 0; col < max_col; col++)
todo[col] = input[MAX(col + bx - ox, 0)];
for (col = max_col; col < 4; col++)
todo[col] = last_col[0];
}
if (duplicate_w[1] && lmax >= 0) // Duplicate last row
for (row = dup_top; row < 4; row++)
{
for (k = 0; k < kmax; k++)
memcpy(&todo[(k + duplicate_h[0]) * 16 + (duplicate_w[0] + lmax) * 4],
&data[(real_y + i + k) * im->cache_entry.w + real_x + j + lmax],
4); // Copy a pixel at a time
for (col = dup_left; col < max_col; col++)
{
if (row < max_row)
{
// Normal copy
todo[row * 4 + col] = input[(row + by - oy) * image_stride + bx + col - ox];
}
else
{
// Copy last line
todo[row * 4 + col] = last_row[col + bx - ox];
}
}
for (col = max_col; col < 4; col++)
{
// Right edge
if (row < max_row)
{
// Duplicate last column
todo[row * 4 + col] = last_col[MAX(row + by - oy, 0) * image_stride];
}
else
{
// Duplicate very last pixel again and again
todo[row * 4 + col] = *last_pix;
}
}
}
rg_etc1_pack_block(offset, (unsigned int*) todo, &param);