PIKApp/plug-ins/file-dds/dxt.c

1527 lines
38 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/*
* DDS PIKA plugin
*
* Copyright (C) 2004-2012 Shawn Kirst <skirst@gmail.com>,
* with parts (C) 2003 Arne Reuter <homepage@arnereuter.de> where specified.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301, USA.
*/
/*
* Parts of this code have been generously released in the public domain
* by Fabian 'ryg' Giesen. The original code can be found (at the time
* of writing) here: http://mollyrocket.com/forums/viewtopic.php?t=392
*
* For more information about this code, see the README.dxt file that
* came with the source.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <glib.h>
#include "dds.h"
#include "dxt.h"
#include "endian_rw.h"
#include "mipmap.h"
#include "imath.h"
#include "vec.h"
#include "dxt_tables.h"
#define SWAP(a, b) do { typeof(a) t; t = a; a = b; b = t; } while(0)
/* SIMD constants */
static const vec4_t V4ZERO = VEC4_CONST1(0.0f);
static const vec4_t V4ONE = VEC4_CONST1(1.0f);
static const vec4_t V4HALF = VEC4_CONST1(0.5f);
static const vec4_t V4ONETHIRD = VEC4_CONST3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f);
static const vec4_t V4TWOTHIRDS = VEC4_CONST3(2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f);
static const vec4_t V4GRID = VEC4_CONST3(31.0f, 63.0f, 31.0f);
static const vec4_t V4GRIDRCP = VEC4_CONST3(1.0f / 31.0f, 1.0f / 63.0f, 1.0f / 31.0f);
static const vec4_t V4EPSILON = VEC4_CONST1(1e-04f);
typedef struct
{
unsigned int single;
unsigned int alphamask;
vec4_t points[16];
vec4_t palette[4];
vec4_t max;
vec4_t min;
vec4_t metric;
} dxtblock_t;
/* extract 4x4 BGRA block */
static void
extract_block (const unsigned char *src,
int x,
int y,
int w,
int h,
unsigned char *block)
{
int i, j;
int bw = MIN(w - x, 4);
int bh = MIN(h - y, 4);
int bx, by;
const int rem[] =
{
0, 0, 0, 0,
0, 1, 0, 1,
0, 1, 2, 0,
0, 1, 2, 3
};
for (i = 0; i < 4; ++i)
{
by = rem[(bh - 1) * 4 + i] + y;
for (j = 0; j < 4; ++j)
{
bx = rem[(bw - 1) * 4 + j] + x;
block[(i * 4 * 4) + (j * 4) + 0] =
src[(by * (w * 4)) + (bx * 4) + 0];
block[(i * 4 * 4) + (j * 4) + 1] =
src[(by * (w * 4)) + (bx * 4) + 1];
block[(i * 4 * 4) + (j * 4) + 2] =
src[(by * (w * 4)) + (bx * 4) + 2];
block[(i * 4 * 4) + (j * 4) + 3] =
src[(by * (w * 4)) + (bx * 4) + 3];
}
}
}
/* pack BGR8 to RGB565 */
static inline unsigned short
pack_rgb565 (const unsigned char *c)
{
return (mul8bit(c[2], 31) << 11) |
(mul8bit(c[1], 63) << 5) |
(mul8bit(c[0], 31) );
}
/* unpack RGB565 to BGR */
static void
unpack_rgb565 (unsigned char *dst,
unsigned short v)
{
int r = (v >> 11) & 0x1f;
int g = (v >> 5) & 0x3f;
int b = (v ) & 0x1f;
dst[0] = (b << 3) | (b >> 2);
dst[1] = (g << 2) | (g >> 4);
dst[2] = (r << 3) | (r >> 2);
}
/* linear interpolation at 1/3 point between a and b */
static void
lerp_rgb13 (unsigned char *dst,
unsigned char *a,
unsigned char *b)
{
#if 0
dst[0] = blerp(a[0], b[0], 0x55);
dst[1] = blerp(a[1], b[1], 0x55);
dst[2] = blerp(a[2], b[2], 0x55);
#else
/*
* according to the S3TC/DX10 specs, this is the correct way to do the
* interpolation (with no rounding bias)
*
* dst = (2 * a + b) / 3;
*/
dst[0] = (2 * a[0] + b[0]) / 3;
dst[1] = (2 * a[1] + b[1]) / 3;
dst[2] = (2 * a[2] + b[2]) / 3;
#endif
}
static void
vec4_endpoints_to_565 (int *start,
int *end,
const vec4_t a,
const vec4_t b)
{
int c[8] __attribute__((aligned(16)));
vec4_t ta = a * V4GRID + V4HALF;
vec4_t tb = b * V4GRID + V4HALF;
#ifdef USE_SSE
# ifdef __SSE2__
const __m128i C565 = _mm_setr_epi16(31, 63, 31, 0, 31, 63, 31, 0);
__m128i ia = _mm_cvttps_epi32(ta);
__m128i ib = _mm_cvttps_epi32(tb);
__m128i zero = _mm_setzero_si128();
__m128i words = _mm_packs_epi32(ia, ib);
words = _mm_min_epi16(C565, _mm_max_epi16(zero, words));
*((__m128i *)&c[0]) = _mm_unpacklo_epi16(words, zero);
*((__m128i *)&c[4]) = _mm_unpackhi_epi16(words, zero);
# else
const __m64 C565 = _mm_setr_pi16(31, 63, 31, 0);
__m64 lo, hi, c0, c1;
__m64 zero = _mm_setzero_si64();
lo = _mm_cvttps_pi32(ta);
hi = _mm_cvttps_pi32(_mm_movehl_ps(ta, ta));
c0 = _mm_packs_pi32(lo, hi);
lo = _mm_cvttps_pi32(tb);
hi = _mm_cvttps_pi32(_mm_movehl_ps(tb, tb));
c1 = _mm_packs_pi32(lo, hi);
c0 = _mm_min_pi16(C565, _mm_max_pi16(zero, c0));
c1 = _mm_min_pi16(C565, _mm_max_pi16(zero, c1));
*((__m64 *)&c[0]) = _mm_unpacklo_pi16(c0, zero);
*((__m64 *)&c[2]) = _mm_unpackhi_pi16(c0, zero);
*((__m64 *)&c[4]) = _mm_unpacklo_pi16(c1, zero);
*((__m64 *)&c[6]) = _mm_unpackhi_pi16(c1, zero);
_mm_empty();
# endif
#else
c[0] = (int)ta[0]; c[4] = (int)tb[0];
c[1] = (int)ta[1]; c[5] = (int)tb[1];
c[2] = (int)ta[2]; c[6] = (int)tb[2];
c[0] = MIN(31, MAX(0, c[0]));
c[1] = MIN(63, MAX(0, c[1]));
c[2] = MIN(31, MAX(0, c[2]));
c[4] = MIN(31, MAX(0, c[4]));
c[5] = MIN(63, MAX(0, c[5]));
c[6] = MIN(31, MAX(0, c[6]));
#endif
*start = ((c[2] << 11) | (c[1] << 5) | c[0]);
*end = ((c[6] << 11) | (c[5] << 5) | c[4]);
}
static void
dxtblock_init (dxtblock_t *dxtb,
const unsigned char *block,
int flags)
{
int i, c0, c;
int bc1 = (flags & DXT_BC1);
float x, y, z;
vec4_t min, max, center, t, cov, inset;
dxtb->single = 1;
dxtb->alphamask = 0;
if(flags & DXT_PERCEPTUAL)
/* ITU-R BT.709 luma coefficients */
dxtb->metric = vec4_set(0.2126f, 0.7152f, 0.0722f, 0.0f);
else
dxtb->metric = vec4_set(1.0f, 1.0f, 1.0f, 0.0f);
c0 = GETL24(block);
for (i = 0; i < 16; ++i)
{
if (bc1 && (block[4 * i + 3] < 128))
dxtb->alphamask |= (3 << (2 * i));
x = (float)block[4 * i + 0] / 255.0f;
y = (float)block[4 * i + 1] / 255.0f;
z = (float)block[4 * i + 2] / 255.0f;
dxtb->points[i] = vec4_set(x, y, z, 0);
c = GETL24(&block[4 * i]);
dxtb->single = dxtb->single && (c == c0);
}
// no need to continue if this is a single color block
if (dxtb->single)
return;
min = vec4_set1(1.0f);
max = vec4_zero();
// get bounding box extents
for (i = 0; i < 16; ++i)
{
min = vec4_min(min, dxtb->points[i]);
max = vec4_max(max, dxtb->points[i]);
}
// select diagonal
center = (max + min) * V4HALF;
cov = vec4_zero();
for (i = 0; i < 16; ++i)
{
t = dxtb->points[i] - center;
cov += t * vec4_splatz(t);
}
#ifdef USE_SSE
{
__m128 mask, tmp;
// get mask
mask = _mm_cmplt_ps(cov, _mm_setzero_ps());
// clear high bits (z, w)
mask = _mm_movelh_ps(mask, _mm_setzero_ps());
// mask and combine
tmp = _mm_or_ps(_mm_and_ps(mask, min), _mm_andnot_ps(mask, max));
min = _mm_or_ps(_mm_and_ps(mask, max), _mm_andnot_ps(mask, min));
max = tmp;
}
#else
{
float x0, x1, y0, y1;
x0 = max[0];
y0 = max[1];
x1 = min[0];
y1 = min[1];
if (cov[0] < 0) SWAP(x0, x1);
if (cov[1] < 0) SWAP(y0, y1);
max[0] = x0;
max[1] = y0;
min[0] = x1;
min[1] = y1;
}
#endif
// inset bounding box and clamp to [0,1]
inset = (max - min) * vec4_set1(1.0f / 16.0f) - vec4_set1((8.0f / 255.0f) / 16.0f);
max = vec4_min(V4ONE, vec4_max(V4ZERO, max - inset));
min = vec4_min(V4ONE, vec4_max(V4ZERO, min + inset));
// clamp to color space and save
dxtb->max = vec4_trunc(V4GRID * max + V4HALF) * V4GRIDRCP;
dxtb->min = vec4_trunc(V4GRID * min + V4HALF) * V4GRIDRCP;
}
static void
construct_palette3 (dxtblock_t *dxtb)
{
dxtb->palette[0] = dxtb->max;
dxtb->palette[1] = dxtb->min;
dxtb->palette[2] = (dxtb->max * V4HALF) + (dxtb->min * V4HALF);
dxtb->palette[3] = vec4_zero();
}
static void
construct_palette4 (dxtblock_t *dxtb)
{
dxtb->palette[0] = dxtb->max;
dxtb->palette[1] = dxtb->min;
dxtb->palette[2] = (dxtb->max * V4TWOTHIRDS) + (dxtb->min * V4ONETHIRD );
dxtb->palette[3] = (dxtb->max * V4ONETHIRD ) + (dxtb->min * V4TWOTHIRDS);
}
/*
* from nvidia-texture-tools; see LICENSE.nvtt for copyright information
*/
static void
optimize_endpoints3 (dxtblock_t *dxtb,
unsigned int indices,
vec4_t *max,
vec4_t *min)
{
float alpha, beta;
vec4_t alpha2_sum, alphax_sum;
vec4_t beta2_sum, betax_sum;
vec4_t alphabeta_sum, a, b, factor;
int i, bits;
alpha2_sum = beta2_sum = alphabeta_sum = vec4_zero();
alphax_sum = vec4_zero();
betax_sum = vec4_zero();
for (i = 0; i < 16; ++i)
{
bits = indices >> (2 * i);
// skip alpha pixels
if ((bits & 3) == 3)
continue;
beta = (float)(bits & 1);
if (bits & 2)
beta = 0.5f;
alpha = 1.0f - beta;
a = vec4_set1(alpha);
b = vec4_set1(beta);
alpha2_sum += a * a;
beta2_sum += b * b;
alphabeta_sum += a * b;
alphax_sum += dxtb->points[i] * a;
betax_sum += dxtb->points[i] * b;
}
factor = alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum;
if (vec4_cmplt(factor, V4EPSILON))
return;
factor = vec4_rcp(factor);
a = (alphax_sum * beta2_sum - betax_sum * alphabeta_sum) * factor;
b = (betax_sum * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
// clamp to the color space
a = vec4_min(V4ONE, vec4_max(V4ZERO, a));
b = vec4_min(V4ONE, vec4_max(V4ZERO, b));
a = vec4_trunc(V4GRID * a + V4HALF) * V4GRIDRCP;
b = vec4_trunc(V4GRID * b + V4HALF) * V4GRIDRCP;
*max = a;
*min = b;
}
/*
* from nvidia-texture-tools; see LICENSE.nvtt for copyright information
*/
static void
optimize_endpoints4 (dxtblock_t *dxtb,
unsigned int indices,
vec4_t *max,
vec4_t *min)
{
float alpha, beta;
vec4_t alpha2_sum, alphax_sum;
vec4_t beta2_sum, betax_sum;
vec4_t alphabeta_sum, a, b, factor;
int i, bits;
alpha2_sum = beta2_sum = alphabeta_sum = vec4_zero();
alphax_sum = vec4_zero();
betax_sum = vec4_zero();
for (i = 0; i < 16; ++i)
{
bits = indices >> (2 * i);
beta = (float)(bits & 1);
if (bits & 2)
beta = (1.0f + beta) / 3.0f;
alpha = 1.0f - beta;
a = vec4_set1(alpha);
b = vec4_set1(beta);
alpha2_sum += a * a;
beta2_sum += b * b;
alphabeta_sum += a * b;
alphax_sum += dxtb->points[i] * a;
betax_sum += dxtb->points[i] * b;
}
factor = alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum;
if (vec4_cmplt(factor, V4EPSILON))
return;
factor = vec4_rcp(factor);
a = (alphax_sum * beta2_sum - betax_sum * alphabeta_sum) * factor;
b = (betax_sum * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
// clamp to the color space
a = vec4_min(V4ONE, vec4_max(V4ZERO, a));
b = vec4_min(V4ONE, vec4_max(V4ZERO, b));
a = vec4_trunc(V4GRID * a + V4HALF) * V4GRIDRCP;
b = vec4_trunc(V4GRID * b + V4HALF) * V4GRIDRCP;
*max = a;
*min = b;
}
static unsigned int
match_colors3 (dxtblock_t *dxtb)
{
int i, idx;
unsigned int indices = 0;
vec4_t t0, t1, t2;
#ifdef USE_SSE
vec4_t d, bits, zero = _mm_setzero_ps();
int mask;
#else
float d0, d1, d2;
#endif
// match each point to the closest color
for (i = 0; i < 16; ++i)
{
// skip alpha pixels
if (((dxtb->alphamask >> (2 * i)) & 3) == 3)
{
indices |= (3 << (2 * i));
continue;
}
t0 = (dxtb->points[i] - dxtb->palette[0]) * dxtb->metric;
t1 = (dxtb->points[i] - dxtb->palette[1]) * dxtb->metric;
t2 = (dxtb->points[i] - dxtb->palette[2]) * dxtb->metric;
#ifdef USE_SSE
_MM_TRANSPOSE4_PS(t0, t1, t2, zero);
d = t0 * t0 + t1 * t1 + t2 * t2;
bits = _mm_cmplt_ps(_mm_shuffle_ps(d, d, _MM_SHUFFLE(3, 1, 0, 0)),
_mm_shuffle_ps(d, d, _MM_SHUFFLE(3, 2, 2, 1)));
mask = _mm_movemask_ps(bits);
if((mask & 3) == 3) idx = 0;
else if(mask & 4) idx = 1;
else idx = 2;
#else
d0 = vec4_dot(t0, t0);
d1 = vec4_dot(t1, t1);
d2 = vec4_dot(t2, t2);
if ((d0 < d1) && (d0 < d2))
idx = 0;
else if (d1 < d2)
idx = 1;
else
idx = 2;
#endif
indices |= (idx << (2 * i));
}
return indices;
}
static unsigned int
match_colors4 (dxtblock_t *dxtb)
{
int i;
unsigned int idx, indices = 0;
unsigned int b0, b1, b2, b3, b4;
unsigned int x0, x1, x2;
vec4_t t0, t1, t2, t3;
#ifdef USE_SSE
vec4_t d;
#else
float d[4];
#endif
// match each point to the closest color
for (i = 0; i < 16; ++i)
{
t0 = (dxtb->points[i] - dxtb->palette[0]) * dxtb->metric;
t1 = (dxtb->points[i] - dxtb->palette[1]) * dxtb->metric;
t2 = (dxtb->points[i] - dxtb->palette[2]) * dxtb->metric;
t3 = (dxtb->points[i] - dxtb->palette[3]) * dxtb->metric;
#ifdef USE_SSE
_MM_TRANSPOSE4_PS(t0, t1, t2, t3);
d = t0 * t0 + t1 * t1 + t2 * t2;
#else
d[0] = vec4_dot(t0, t0);
d[1] = vec4_dot(t1, t1);
d[2] = vec4_dot(t2, t2);
d[3] = vec4_dot(t3, t3);
#endif
b0 = d[0] > d[3];
b1 = d[1] > d[2];
b2 = d[0] > d[2];
b3 = d[1] > d[3];
b4 = d[2] > d[3];
x0 = b1 & b2;
x1 = b0 & b3;
x2 = b0 & b4;
idx = x2 | ((x0 | x1) << 1);
indices |= (idx << (2 * i));
}
return indices;
}
static float
compute_error3 (dxtblock_t *dxtb,
unsigned int indices)
{
int i, idx;
float error = 0;
vec4_t t;
// compute error
for (i = 0; i < 16; ++i)
{
idx = (indices >> (2 * i)) & 3;
// skip alpha pixels
if(idx == 3)
continue;
t = (dxtb->points[i] - dxtb->palette[idx]) * dxtb->metric;
error += vec4_dot(t, t);
}
return error;
}
static float
compute_error4 (dxtblock_t *dxtb,
unsigned int indices)
{
int i, idx;
float error = 0;
#ifdef USE_SSE
vec4_t a0, a1, a2, a3;
vec4_t b0, b1, b2, b3;
vec4_t d;
for (i = 0; i < 4; ++i)
{
idx = indices >> (8 * i);
a0 = dxtb->points[4 * i + 0];
a1 = dxtb->points[4 * i + 1];
a2 = dxtb->points[4 * i + 2];
a3 = dxtb->points[4 * i + 3];
b0 = dxtb->palette[(idx ) & 3];
b1 = dxtb->palette[(idx >> 2) & 3];
b2 = dxtb->palette[(idx >> 4) & 3];
b3 = dxtb->palette[(idx >> 6) & 3];
a0 = (a0 - b0) * dxtb->metric;
a1 = (a1 - b1) * dxtb->metric;
a2 = (a2 - b2) * dxtb->metric;
a3 = (a3 - b3) * dxtb->metric;
_MM_TRANSPOSE4_PS(a0, a1, a2, a3);
d = a0 * a0 + a1 * a1 + a2 * a2;
error += vec4_accum(d);
}
#else
vec4_t t;
// compute error
for (i = 0; i < 16; ++i)
{
idx = (indices >> (2 * i)) & 3;
t = (dxtb->points[i] - dxtb->palette[idx]) * dxtb->metric;
error += vec4_dot(t, t);
}
#endif
return error;
}
static unsigned int
compress3 (dxtblock_t *dxtb)
{
const int MAX_ITERATIONS = 8;
int i;
unsigned int indices, bestindices;
float error, besterror = FLT_MAX;
vec4_t oldmax, oldmin;
construct_palette3(dxtb);
indices = match_colors3(dxtb);
bestindices = indices;
for (i = 0; i < MAX_ITERATIONS; ++i)
{
oldmax = dxtb->max;
oldmin = dxtb->min;
optimize_endpoints3(dxtb, indices, &dxtb->max, &dxtb->min);
construct_palette3(dxtb);
indices = match_colors3(dxtb);
error = compute_error3(dxtb, indices);
if (error < besterror)
{
besterror = error;
bestindices = indices;
}
else
{
dxtb->max = oldmax;
dxtb->min = oldmin;
break;
}
}
return bestindices;
}
static unsigned int
compress4 (dxtblock_t *dxtb)
{
const int MAX_ITERATIONS = 8;
int i;
unsigned int indices, bestindices;
float error, besterror = FLT_MAX;
vec4_t oldmax, oldmin;
construct_palette4(dxtb);
indices = match_colors4(dxtb);
bestindices = indices;
for (i = 0; i < MAX_ITERATIONS; ++i)
{
oldmax = dxtb->max;
oldmin = dxtb->min;
optimize_endpoints4(dxtb, indices, &dxtb->max, &dxtb->min);
construct_palette4(dxtb);
indices = match_colors4(dxtb);
error = compute_error4(dxtb, indices);
if (error < besterror)
{
besterror = error;
bestindices = indices;
}
else
{
dxtb->max = oldmax;
dxtb->min = oldmin;
break;
}
}
return bestindices;
}
static void
encode_color_block (unsigned char *dst,
unsigned char *block,
int flags)
{
dxtblock_t dxtb;
int max16, min16;
unsigned int indices, mask;
dxtblock_init(&dxtb, block, flags);
if (dxtb.single) // single color block
{
max16 = (omatch5[block[2]][0] << 11) |
(omatch6[block[1]][0] << 5) |
(omatch5[block[0]][0] );
min16 = (omatch5[block[2]][1] << 11) |
(omatch6[block[1]][1] << 5) |
(omatch5[block[0]][1] );
indices = 0xaaaaaaaa; // 101010...
if ((flags & DXT_BC1) && dxtb.alphamask)
{
// DXT1 compression, non-opaque block. Add alpha indices.
indices |= dxtb.alphamask;
if (max16 > min16)
SWAP(max16, min16);
}
else if (max16 < min16)
{
SWAP(max16, min16);
indices ^= 0x55555555; // 010101...
}
}
else if ((flags & DXT_BC1) && dxtb.alphamask) // DXT1 compression, non-opaque block
{
indices = compress3(&dxtb);
vec4_endpoints_to_565(&max16, &min16, dxtb.max, dxtb.min);
if (max16 > min16)
{
SWAP(max16, min16);
// remap indices 0 -> 1, 1 -> 0
mask = indices & 0xaaaaaaaa;
mask = mask | (mask >> 1);
indices = (indices & mask) | ((indices ^ 0x55555555) & ~mask);
}
}
else
{
indices = compress4(&dxtb);
vec4_endpoints_to_565(&max16, &min16, dxtb.max, dxtb.min);
if (max16 < min16)
{
SWAP(max16, min16);
indices ^= 0x55555555; // 010101...
}
}
PUTL16(dst + 0, max16);
PUTL16(dst + 2, min16);
PUTL32(dst + 4, indices);
}
static void
get_min_max_YCoCg (const unsigned char *block,
unsigned char *mincolor,
unsigned char *maxcolor)
{
int i;
mincolor[2] = mincolor[1] = 255;
maxcolor[2] = maxcolor[1] = 0;
for (i = 0; i < 16; ++i)
{
if (block[4 * i + 2] < mincolor[2]) mincolor[2] = block[4 * i + 2];
if (block[4 * i + 1] < mincolor[1]) mincolor[1] = block[4 * i + 1];
if (block[4 * i + 2] > maxcolor[2]) maxcolor[2] = block[4 * i + 2];
if (block[4 * i + 1] > maxcolor[1]) maxcolor[1] = block[4 * i + 1];
}
}
static void
scale_YCoCg (unsigned char *block,
unsigned char *mincolor,
unsigned char *maxcolor)
{
const int s0 = 128 / 2 - 1;
const int s1 = 128 / 4 - 1;
int m0, m1, m2, m3;
int mask0, mask1, scale;
int i;
m0 = abs(mincolor[2] - 128);
m1 = abs(mincolor[1] - 128);
m2 = abs(maxcolor[2] - 128);
m3 = abs(maxcolor[1] - 128);
if (m1 > m0) m0 = m1;
if (m3 > m2) m2 = m3;
if (m2 > m0) m0 = m2;
mask0 = -(m0 <= s0);
mask1 = -(m0 <= s1);
scale = 1 + (1 & mask0) + (2 & mask1);
mincolor[2] = (mincolor[2] - 128) * scale + 128;
mincolor[1] = (mincolor[1] - 128) * scale + 128;
mincolor[0] = (scale - 1) << 3;
maxcolor[2] = (maxcolor[2] - 128) * scale + 128;
maxcolor[1] = (maxcolor[1] - 128) * scale + 128;
maxcolor[0] = (scale - 1) << 3;
for (i = 0; i < 16; ++i)
{
block[i * 4 + 2] = (block[i * 4 + 2] - 128) * scale + 128;
block[i * 4 + 1] = (block[i * 4 + 1] - 128) * scale + 128;
}
}
#define INSET_SHIFT 4
static void
inset_bbox_YCoCg (unsigned char *mincolor,
unsigned char *maxcolor)
{
int inset[4], mini[4], maxi[4];
inset[2] = (maxcolor[2] - mincolor[2]) - ((1 << (INSET_SHIFT - 1)) - 1);
inset[1] = (maxcolor[1] - mincolor[1]) - ((1 << (INSET_SHIFT - 1)) - 1);
mini[2] = ((mincolor[2] << INSET_SHIFT) + inset[2]) >> INSET_SHIFT;
mini[1] = ((mincolor[1] << INSET_SHIFT) + inset[1]) >> INSET_SHIFT;
maxi[2] = ((maxcolor[2] << INSET_SHIFT) - inset[2]) >> INSET_SHIFT;
maxi[1] = ((maxcolor[1] << INSET_SHIFT) - inset[1]) >> INSET_SHIFT;
mini[2] = (mini[2] >= 0) ? mini[2] : 0;
mini[1] = (mini[1] >= 0) ? mini[1] : 0;
maxi[2] = (maxi[2] <= 255) ? maxi[2] : 255;
maxi[1] = (maxi[1] <= 255) ? maxi[1] : 255;
mincolor[2] = (mini[2] & 0xf8) | (mini[2] >> 5);
mincolor[1] = (mini[1] & 0xfc) | (mini[1] >> 6);
maxcolor[2] = (maxi[2] & 0xf8) | (maxi[2] >> 5);
maxcolor[1] = (maxi[1] & 0xfc) | (maxi[1] >> 6);
}
static void
select_diagonal_YCoCg (const unsigned char *block,
unsigned char *mincolor,
unsigned char *maxcolor)
{
unsigned char mid0, mid1, side, mask, b0, b1, c0, c1;
int i;
mid0 = ((int)mincolor[2] + maxcolor[2] + 1) >> 1;
mid1 = ((int)mincolor[1] + maxcolor[1] + 1) >> 1;
side = 0;
for (i = 0; i < 16; ++i)
{
b0 = block[i * 4 + 2] >= mid0;
b1 = block[i * 4 + 1] >= mid1;
side += (b0 ^ b1);
}
mask = -(side > 8);
mask &= -(mincolor[2] != maxcolor[2]);
c0 = mincolor[1];
c1 = maxcolor[1];
c0 ^= c1;
c1 ^= c0 & mask;
c0 ^= c1;
mincolor[1] = c0;
maxcolor[1] = c1;
}
static void
encode_YCoCg_block (unsigned char *dst,
unsigned char *block)
{
unsigned char colors[4][3], *maxcolor, *mincolor;
unsigned int mask;
int c0, c1, d0, d1, d2, d3;
int b0, b1, b2, b3, b4;
int x0, x1, x2;
int i, idx;
maxcolor = &colors[0][0];
mincolor = &colors[1][0];
get_min_max_YCoCg(block, mincolor, maxcolor);
scale_YCoCg(block, mincolor, maxcolor);
inset_bbox_YCoCg(mincolor, maxcolor);
select_diagonal_YCoCg(block, mincolor, maxcolor);
lerp_rgb13(&colors[2][0], maxcolor, mincolor);
lerp_rgb13(&colors[3][0], mincolor, maxcolor);
mask = 0;
for (i = 0; i < 16; ++i)
{
c0 = block[4 * i + 2];
c1 = block[4 * i + 1];
d0 = abs(colors[0][2] - c0) + abs(colors[0][1] - c1);
d1 = abs(colors[1][2] - c0) + abs(colors[1][1] - c1);
d2 = abs(colors[2][2] - c0) + abs(colors[2][1] - c1);
d3 = abs(colors[3][2] - c0) + abs(colors[3][1] - c1);
b0 = d0 > d3;
b1 = d1 > d2;
b2 = d0 > d2;
b3 = d1 > d3;
b4 = d2 > d3;
x0 = b1 & b2;
x1 = b0 & b3;
x2 = b0 & b4;
idx = (x2 | ((x0 | x1) << 1));
mask |= idx << (2 * i);
}
PUTL16(dst + 0, pack_rgb565(maxcolor));
PUTL16(dst + 2, pack_rgb565(mincolor));
PUTL32(dst + 4, mask);
}
/* write DXT3 alpha block */
static void
encode_alpha_block_BC2 (unsigned char *dst,
const unsigned char *block)
{
int i, a1, a2;
block += 3;
for (i = 0; i < 8; ++i)
{
a1 = mul8bit(block[8 * i + 0], 0x0f);
a2 = mul8bit(block[8 * i + 4], 0x0f);
*dst++ = (a2 << 4) | a1;
}
}
/* Write DXT5 alpha block */
static void
encode_alpha_block_BC3 (unsigned char *dst,
const unsigned char *block,
const int offset)
{
int i, v, mn, mx;
int dist, bias, dist2, dist4, bits, mask;
int a, idx, t;
block += offset;
block += 3;
/* find min/max alpha pair */
mn = mx = block[0];
for (i = 0; i < 16; ++i)
{
v = block[4 * i];
if(v > mx) mx = v;
if(v < mn) mn = v;
}
/* encode them */
*dst++ = mx;
*dst++ = mn;
/*
* determine bias and emit indices
* given the choice of mx/mn, these indices are optimal:
* http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/
*/
dist = mx - mn;
dist4 = dist * 4;
dist2 = dist * 2;
bias = (dist < 8) ? (dist - 1) : (dist / 2 + 2);
bias -= mn * 7;
bits = 0;
mask = 0;
for (i = 0; i < 16; ++i)
{
a = block[4 * i] * 7 + bias;
/* select index. this is a "linear scale" lerp factor between 0
(val=min) and 7 (val=max). */
t = (a >= dist4) ? -1 : 0; idx = t & 4; a -= dist4 & t;
t = (a >= dist2) ? -1 : 0; idx += t & 2; a -= dist2 & t;
idx += (a >= dist);
/* turn linear scale into DXT index (0/1 are extremal pts) */
idx = -idx & 7;
idx ^= (2 > idx);
/* write index */
mask |= idx << bits;
if ((bits += 3) >= 8)
{
*dst++ = mask;
mask >>= 8;
bits -= 8;
}
}
}
#define BLOCK_COUNT(w, h) ((((h) + 3) >> 2) * (((w) + 3) >> 2))
#define BLOCK_OFFSET(x, y, w, bs) (((y) >> 2) * ((bs) * (((w) + 3) >> 2)) + ((bs) * ((x) >> 2)))
static void
compress_BC1 (unsigned char *dst,
const unsigned char *src,
int w,
int h,
int flags)
{
const unsigned int block_count = BLOCK_COUNT(w, h);
unsigned int i;
unsigned char block[64], *p;
int x, y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 8);
extract_block(src, x, y, w, h, block);
encode_color_block(p, block, DXT_BC1 | flags);
}
}
static void
compress_BC2 (unsigned char *dst,
const unsigned char *src,
int w,
int h,
int flags)
{
const unsigned int block_count = BLOCK_COUNT(w, h);
unsigned int i;
unsigned char block[64], *p;
int x, y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
encode_alpha_block_BC2(p, block);
encode_color_block(p + 8, block, DXT_BC2 | flags);
}
}
static void
compress_BC3 (unsigned char *dst,
const unsigned char *src,
int w,
int h,
int flags)
{
const unsigned int block_count = BLOCK_COUNT(w, h);
unsigned int i;
unsigned char block[64], *p;
int x, y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
encode_alpha_block_BC3(p, block, 0);
encode_color_block(p + 8, block, DXT_BC3 | flags);
}
}
static void
compress_BC4 (unsigned char *dst,
const unsigned char *src,
int w,
int h)
{
const unsigned int block_count = BLOCK_COUNT(w, h);
unsigned int i;
unsigned char block[64], *p;
int x, y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 8);
extract_block(src, x, y, w, h, block);
encode_alpha_block_BC3(p, block, -1);
}
}
static void
compress_BC5 (unsigned char *dst,
const unsigned char *src,
int w,
int h)
{
const unsigned int block_count = BLOCK_COUNT(w, h);
unsigned int i;
unsigned char block[64], *p;
int x, y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
/* Pixels are ordered as BGRA (see write_layer)
* First we encode red -1+3: channel 2;
* then we encode green -2+3: channel 1.
*/
encode_alpha_block_BC3(p, block, -1);
encode_alpha_block_BC3(p + 8, block, -2);
}
}
static void
compress_YCoCg (unsigned char *dst,
const unsigned char *src,
int w,
int h)
{
const unsigned int block_count = BLOCK_COUNT(w, h);
unsigned int i;
unsigned char block[64], *p;
int x, y;
#ifdef _OPENMP
#pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
#endif
for (i = 0; i < block_count; ++i)
{
x = (i % ((w + 3) >> 2)) << 2;
y = (i / ((w + 3) >> 2)) << 2;
p = dst + BLOCK_OFFSET(x, y, w, 16);
extract_block(src, x, y, w, h, block);
encode_alpha_block_BC3(p, block, 0);
encode_YCoCg_block(p + 8, block);
}
}
int
dxt_compress (unsigned char *dst,
unsigned char *src,
int format,
unsigned int width,
unsigned int height,
int bpp,
int mipmaps,
int flags)
{
int i, size, w, h;
unsigned int offset;
unsigned char *tmp = NULL;
int j;
unsigned char *s;
if (bpp == 1)
{
/* grayscale promoted to BGRA */
size = get_mipmapped_size(width, height, 4, 0, mipmaps,
DDS_COMPRESS_NONE);
tmp = g_malloc(size);
for (i = j = 0; j < size; ++i, j += 4)
{
tmp[j + 0] = src[i];
tmp[j + 1] = src[i];
tmp[j + 2] = src[i];
tmp[j + 3] = 255;
}
bpp = 4;
}
else if (bpp == 2)
{
/* gray-alpha promoted to BGRA */
size = get_mipmapped_size(width, height, 4, 0, mipmaps,
DDS_COMPRESS_NONE);
tmp = g_malloc(size);
for (i = j = 0; j < size; i += 2, j += 4)
{
tmp[j + 0] = src[i];
tmp[j + 1] = src[i];
tmp[j + 2] = src[i];
tmp[j + 3] = src[i + 1];
}
bpp = 4;
}
else if (bpp == 3)
{
size = get_mipmapped_size(width, height, 4, 0, mipmaps,
DDS_COMPRESS_NONE);
tmp = g_malloc(size);
for (i = j = 0; j < size; i += 3, j += 4)
{
tmp[j + 0] = src[i + 0];
tmp[j + 1] = src[i + 1];
tmp[j + 2] = src[i + 2];
tmp[j + 3] = 255;
}
bpp = 4;
}
offset = 0;
w = width;
h = height;
s = tmp ? tmp : src;
for (i = 0; i < mipmaps; ++i)
{
switch (format)
{
case DDS_COMPRESS_BC1:
compress_BC1(dst + offset, s, w, h, flags);
break;
case DDS_COMPRESS_BC2:
compress_BC2(dst + offset, s, w, h, flags);
break;
case DDS_COMPRESS_BC3:
case DDS_COMPRESS_BC3N:
case DDS_COMPRESS_RXGB:
case DDS_COMPRESS_AEXP:
case DDS_COMPRESS_YCOCG:
compress_BC3(dst + offset, s, w, h, flags);
break;
case DDS_COMPRESS_BC4:
compress_BC4(dst + offset, s, w, h);
break;
case DDS_COMPRESS_BC5:
compress_BC5(dst + offset, s, w, h);
break;
case DDS_COMPRESS_YCOCGS:
compress_YCoCg(dst + offset, s, w, h);
break;
default:
compress_BC3(dst + offset, s, w, h, flags);
break;
}
s += (w * h * bpp);
offset += get_mipmapped_size(w, h, 0, 0, 1, format);
w = MAX(1, w >> 1);
h = MAX(1, h >> 1);
}
if (tmp)
g_free(tmp);
return 1;
}
static void
decode_color_block (unsigned char *block,
unsigned char *src,
int format)
{
int i, x, y;
unsigned char *d = block;
unsigned int indices, idx;
unsigned char colors[4][3];
unsigned short c0, c1;
c0 = GETL16(&src[0]);
c1 = GETL16(&src[2]);
unpack_rgb565(colors[0], c0);
unpack_rgb565(colors[1], c1);
if ((c0 > c1) || (format == DDS_COMPRESS_BC3))
{
lerp_rgb13(colors[2], colors[0], colors[1]);
lerp_rgb13(colors[3], colors[1], colors[0]);
}
else
{
for (i = 0; i < 3; ++i)
{
colors[2][i] = (colors[0][i] + colors[1][i] + 1) >> 1;
colors[3][i] = 255;
}
}
src += 4;
for (y = 0; y < 4; ++y)
{
indices = src[y];
for (x = 0; x < 4; ++x)
{
idx = indices & 0x03;
d[0] = colors[idx][2];
d[1] = colors[idx][1];
d[2] = colors[idx][0];
if (format == DDS_COMPRESS_BC1)
d[3] = ((c0 <= c1) && idx == 3) ? 0 : 255;
indices >>= 2;
d += 4;
}
}
}
static void
decode_alpha_block_BC2 (unsigned char *block,
unsigned char *src)
{
int x, y;
unsigned char *d = block;
unsigned int bits;
for (y = 0; y < 4; ++y)
{
bits = GETL16(&src[2 * y]);
for (x = 0; x < 4; ++x)
{
d[0] = (bits & 0x0f) * 17;
bits >>= 4;
d += 4;
}
}
}
static void
decode_alpha_block_BC3 (unsigned char *block,
unsigned char *src,
int w)
{
int x, y, code;
unsigned char *d = block;
unsigned char a0 = src[0];
unsigned char a1 = src[1];
unsigned long long bits = GETL64(src) >> 16;
for (y = 0; y < 4; ++y)
{
for (x = 0; x < 4; ++x)
{
code = ((unsigned int)bits) & 0x07;
if (code == 0)
d[0] = a0;
else if (code == 1)
d[0] = a1;
else if (a0 > a1)
d[0] = ((8 - code) * a0 + (code - 1) * a1) / 7;
else if (code >= 6)
d[0] = (code == 6) ? 0 : 255;
else
d[0] = ((6 - code) * a0 + (code - 1) * a1) / 5;
bits >>= 3;
d += 4;
}
if (w < 4)
bits >>= (3 * (4 - w));
}
}
static void
make_normal (unsigned char *dst,
unsigned char x,
unsigned char y)
{
float nx = 2.0f * ((float)x / 255.0f) - 1.0f;
float ny = 2.0f * ((float)y / 255.0f) - 1.0f;
float nz = 0.0f;
float d = 1.0f - nx * nx + ny * ny;
int z;
if (d > 0)
nz = sqrtf(d);
z = (int)(255.0f * (nz + 1) / 2.0f);
z = MAX(0, MIN(255, z));
dst[0] = x;
dst[1] = y;
dst[2] = z;
}
static void
normalize_block (unsigned char *block,
int format)
{
int x, y, tmp;
for (y = 0; y < 4; ++y)
{
for (x = 0; x < 4; ++x)
{
if (format == DDS_COMPRESS_BC3)
{
tmp = block[y * 16 + (x * 4)];
make_normal(&block[y * 16 + (x * 4)],
block[y * 16 + (x * 4) + 3],
block[y * 16 + (x * 4) + 1]);
block[y * 16 + (x * 4) + 3] = tmp;
}
else if (format == DDS_COMPRESS_BC5)
{
make_normal(&block[y * 16 + (x * 4)],
block[y * 16 + (x * 4)],
block[y * 16 + (x * 4) + 1]);
}
}
}
}
static void
put_block (unsigned char *dst,
unsigned char *block,
unsigned int bx,
unsigned int by,
unsigned int width,
unsigned height,
int bpp)
{
int x, y, i;
unsigned char *d;
for (y = 0; y < 4 && ((by + y) < height); ++y)
{
d = dst + ((y + by) * width + bx) * bpp;
for (x = 0; x < 4 && ((bx + x) < width); ++x)
{
for (i = 0; i < bpp; ++ i)
*d++ = block[y * 16 + (x * 4) + i];
}
}
}
int
dxt_decompress (unsigned char *dst,
unsigned char *src,
int format,
unsigned int size,
unsigned int width,
unsigned int height,
int bpp,
int normals)
{
unsigned char *s;
unsigned int x, y;
unsigned char block[16 * 4];
s = src;
for (y = 0; y < height; y += 4)
{
for (x = 0; x < width; x += 4)
{
memset(block, 0, 16 * 4);
if (format == DDS_COMPRESS_BC1)
{
decode_color_block(block, s, format);
s += 8;
}
else if (format == DDS_COMPRESS_BC2)
{
decode_alpha_block_BC2(block + 3, s);
decode_color_block(block, s + 8, format);
s += 16;
}
else if (format == DDS_COMPRESS_BC3)
{
decode_alpha_block_BC3(block + 3, s, width);
decode_color_block(block, s + 8, format);
s += 16;
}
else if (format == DDS_COMPRESS_BC4)
{
decode_alpha_block_BC3(block, s, width);
s += 8;
}
else if (format == DDS_COMPRESS_BC5)
{
decode_alpha_block_BC3(block, s, width);
decode_alpha_block_BC3(block + 1, s + 8, width);
s += 16;
}
if (normals)
normalize_block(block, format);
put_block(dst, block, x, y, width, height, bpp);
}
}
return 1;
}