From 28ce791dfa547b7e29641892b1b7931a007e665d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Andre Date: Wed, 23 Apr 2014 18:08:36 +0900 Subject: [PATCH] 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). --- src/lib/eet/eet_image.c | 217 ++++++++++++---------- src/modules/evas/savers/tgv/evas_image_save_tgv.c | 175 ++++++++++------- 2 files changed, 232 insertions(+), 160 deletions(-) diff --git a/src/lib/eet/eet_image.c b/src/lib/eet/eet_image.c index 58e7df5..4aacee1 100644 --- a/src/lib/eet/eet_image.c +++ b/src/lib/eet/eet_image.c @@ -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]: 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[4] = (block << 4) | block; + // 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; + + if (y == 0) real_y = 0; + else if (y < image_height + 1) real_y = y - 1; + else real_y = image_height - 1; - for (x = 0; x < w + 2; x += block) + 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; - - for (i = 0; i < block; i += 4) - { - unsigned char block_h; - int kmax; + if (x == 0) real_x = 0; + else if (x < image_stride + 1) real_x = x - 1; + else real_x = image_stride - 1; - 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]; + 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; - kmax = real_y + i + block_h < h ? - block_h : h - real_y - i - 1; + for (int by = 0; by < macro_block_height; by += 4) + { + 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; - 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; - - - 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; + 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; - 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, ¶m); offset += 8; } } - if (compression) + if (compress) { wlen = LZ4_compressHC(buffer, comp, block_count * 8); } diff --git a/src/modules/evas/savers/tgv/evas_image_save_tgv.c b/src/modules/evas/savers/tgv/evas_image_save_tgv.c index 357dfff..84314b5 100644 --- a/src/modules/evas/savers/tgv/evas_image_save_tgv.c +++ b/src/modules/evas/savers/tgv/evas_image_save_tgv.c @@ -14,6 +14,18 @@ #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, int quality, int compress) @@ -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; + + if (y == 0) real_y = 0; + else if (y < image_height + 1) real_y = y - 1; + else real_y = image_height - 1; - for (x = 0; x < im->cache_entry.w + 2; x += block) + 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; - - for (i = 0; i < block; i += 4) - { - unsigned char block_h; - int kmax; + if (x == 0) real_x = 0; + else if (x < image_stride + 1) real_x = x - 1; + else real_x = image_stride - 1; - 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]; + 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; - kmax = real_y + i + block_h < im->cache_entry.h ? - block_h : im->cache_entry.h - real_y - i - 1; + for (int by = 0; by < macro_block_height; by += 4) + { + 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; - 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; - - 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]; + 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; - 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, ¶m); -- 2.7.4